<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script id="versionArea" type="text/javascript">
//<![CDATA[
var version = {title: "TiddlyWiki", major: 2, minor: 4, revision: 0, date: new Date("May 9, 2008"), extensions: {}};
//]]>
</script>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="copyright" content="
TiddlyWiki created by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)

Copyright (c) UnaMesa Association 2004-2008

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

Neither the name of the UnaMesa Association nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
" />
<!--PRE-HEAD-START-->
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
<!--}}}-->
<!--PRE-HEAD-END-->
<title> VBC - Drupal Haute Performances </title>
<style id="styleArea" type="text/css">
#saveTest {display:none;}
#messageArea {display:none;}
#copyright {display:none;}
#storeArea {display:none;}
#storeArea div {padding:0.5em; margin:1em 0em 0em 0em; border-color:#fff #666 #444 #ddd; border-style:solid; border-width:2px; overflow:auto;}
#shadowArea {display:none;}
#javascriptWarning {width:100%; text-align:center; font-weight:bold; background-color:#dd1100; color:#fff; padding:1em 0em;}
</style>
<!--POST-HEAD-START-->

<!--POST-HEAD-END-->
</head>
<body onload="main();" onunload="if(window.checkUnsavedChanges) checkUnsavedChanges(); if(window.scrubNodes) scrubNodes(document.body);">
<!--PRE-BODY-START-->

<!--PRE-BODY-END-->
<div id="copyright">
Welcome to TiddlyWiki created by Jeremy Ruston, Copyright &copy; 2007 UnaMesa Association
</div>
<noscript>
	<div id="javascriptWarning">This page requires JavaScript to function properly.<br /><br />If you are using Microsoft Internet Explorer you may need to click on the yellow bar above and select 'Allow Blocked Content'. You must then click 'Yes' on the following security warning.</div>
</noscript>
<div id="saveTest"></div>
<div id="backstageCloak"></div>
<div id="backstageButton"></div>
<div id="backstageArea"><div id="backstageToolbar"></div></div>
<div id="backstage">
	<div id="backstagePanel"></div>
</div>
<div id="contentWrapper"></div>
<div id="contentStash"></div>
<div id="shadowArea">
<div title="MarkupPreHead">
<pre>&lt;!--{{{--&gt;
&lt;link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="ColorPalette">
<pre>Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88</pre>
</div>
<div title="StyleSheetColors">
<pre>/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

.tabSelected{color:[[ColorPalette::PrimaryDark]];
	background:[[ColorPalette::TertiaryPale]];
	border-left:1px solid [[ColorPalette::TertiaryLight]];
	border-top:1px solid [[ColorPalette::TertiaryLight]];
	border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
	border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}

#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.tiddler .defaultCommand {font-weight:bold;}

.shadow .title {color:[[ColorPalette::TertiaryDark]];}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.imageLink, #displayArea .imageLink {background:transparent;}

.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/</pre>
</div>
<div title="StyleSheetLayout">
<pre>/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
	#mainMenu .tiddlyLinkNonExisting,
	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}

.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0em 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler {padding:1em 1em 0em 1em;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}

.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

.sparkline {line-height:1em;}
.sparktick {outline:0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/</pre>
</div>
<div title="StyleSheetLocale">
<pre>/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/</pre>
</div>
<div title="StyleSheetPrint">
<pre>/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*}}}*/</pre>
</div>
<div title="PageTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
&lt;div class='headerShadow'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class='headerForeground'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
&lt;div id='sidebar'&gt;
&lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
&lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id='displayArea'&gt;
&lt;div id='messageArea'&gt;&lt;/div&gt;
&lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="ViewTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="EditTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div macro='annotations'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="GettingStarted">
<pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* MainMenu: The menu (usually on the left)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
</div>
<div title="OptionsPanel">
<pre>These InterfaceOptions for customising TiddlyWiki are saved in your browser

Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)

&lt;&lt;option txtUserName&gt;&gt;
&lt;&lt;option chkSaveBackups&gt;&gt; SaveBackups
&lt;&lt;option chkAutoSave&gt;&gt; AutoSave
&lt;&lt;option chkRegExpSearch&gt;&gt; RegExpSearch
&lt;&lt;option chkCaseSensitiveSearch&gt;&gt; CaseSensitiveSearch
&lt;&lt;option chkAnimate&gt;&gt; EnableAnimations

----
Also see AdvancedOptions</pre>
</div>
<div title="ImportTiddlers">
<pre>&lt;&lt;importTiddlers&gt;&gt;</pre>
</div>
</div>
<!--POST-SHADOWAREA-->
<div id="storeArea">
<div title="(default) on http://bradleymeck.tiddlyspot.com/" modifier="(System)" created="200807211230" tags="systemServer" changecount="1">
<pre>|''Type:''|file|
|''URL:''|http://bradleymeck.tiddlyspot.com/|
|''Workspace:''|(default)|

This tiddler was automatically created to record the details of this server</pre>
</div>
<div title="(default) on http://www.tiddlytools.com" modifier="(System)" created="200807211232" tags="systemServer" changecount="1">
<pre>|''Type:''|file|
|''URL:''|http://www.tiddlytools.com|
|''Workspace:''|(default)|

This tiddler was automatically created to record the details of this server</pre>
</div>
<div title="(default) on http://www.tiddlywiki.com/coreplugins.html" modifier="(System)" created="200807211446" tags="systemServer" changecount="1">
<pre>|''Type:''|file|
|''URL:''|http://www.tiddlywiki.com/coreplugins.html|
|''Workspace:''|(default)|

This tiddler was automatically created to record the details of this server</pre>
</div>
<div title="Aide" modifier="YBA" created="200809191150" modified="200809221203" changecount="22">
<pre>!Philosphie
* 1 fichier TiddlyWiki par projet Scrum
* Les plugins sont le moins intrusifs possibles, c'est à dire qu'il est possible de fonctionner sans eux dans l'absolu, a tout moment.
* Le fichier contient tout ce dont vous avez besoin pour 
** Gérer les UserStory et les backlog de produit ou de sprint
** Gérer les tâches, saisir et afficher les burdown chart
** Collaborer avec d'autres personnes sur un même fichier
!Les plugins installés
* SortListPlugin permet de réordonner le contenu d'un Tiddler facilement
* [[Populate/UpdateListPlugin]] permet de préremplir ou de mettre à jour une liste de Tiddler
* SliceGridPlugin permet de saisir le reste à faire quotidien sur les tâches
* HistoGramMacro permet d'afficher un burndown chart sur la base des SliceGrid
* UploadPlugin et UploadTiddlerPlugin permet de collaborer a plusieurs sur un même ficher
* ForEachTiddlerPlugin permet d'afficher des listes via un mini langage SQL) de tiddler pour avoir des tableaux de bord
!Notions
* un ''tiddler'' est une page de ce wiki, qui contient du texte au format wiki ou bien du code javascript si c'est un plugin
* les tiddlers peuvent être ''taggés'' par des mots clefs qui peuvent référencer d'autres tiddlers. Ceci permet de créer des relations (0-n) entre tiddlers. Par exemple une UserStory peut être rattachée a un sprint en lui rajoutant le nom du sprint comme tag.
* Dans cet outil un ''backlog'' est une liste ''ordonnée'' de tiddlers. Il s'agit d'un lien (0-n) entre tiddlers, mais un lien ''ordonné'', alors que les tags ne permettent pas de saisir un ordre.
* Les tiddlers peuvent contenir du texte, mais aussi des valeurs. Ils servent parfois de paramétrage, comme le tiddler SprintDays qui contient la liste des jours d'un sprint.
* Les tiddlers peuvent contenir des valeurs.
** Les ''fields'' sont des valeurs non visibles (il faut cliquer sur more/fields dans la barre d'outils pour les afficher) et nommées, associées au tiddler
** les ''slice'' sont des valeurs stockées dans le texte du tiddler d'une manière particulière : la notation tableau est utilisées |nom_slice|valeur_slice|. Les slices sont utilisées pour stocker les valeurs du reste a faire quotidien chaque tâche.
!Gérer votre projet
* Commencez par définir les [[Paramètres]]
* Lancez le [[Sprint 0]] qui devrait contenir tout ce qui se passe avant de commencer a coder. C'est à dire, la création du backlog initial ordonné, et le premier découpage en tâches.
* Une fois votre [[Backlog de produit]] créé vous pouvez démarrer un nouveau sprint. Cliquez sur &lt;new Sprint&gt; dans le menu de droite, ou bien créé un nouveau tiddler et taggez le &quot;sprint&quot; (c'est la même chose). Le fait de tagger sprint ajoute automatiquement une liste triable, ainsi que le burndownchart dans la visualisation du tiddler. Le sprint doit définir une liste de UserStories retenues, et leur découpage en tâches. 
* Les UserStories peuvent être crées de différentes manières
** Soit vous cliquez sur le bouton &quot;Add UserStory&quot; a la fin du backlog, ce qui va créer une UserStory vide
** ou bien avec le button &quot;New UserStory&quot; sur la droite
** soit vous créé un tiddler avec le tag UserStory
** soit vous écrivez toutes vos UserStory en texte (une UserStory par ligne) dans un tiddler, que vous taggé backlog, ensuite vous utilisez le bouton update qui va transformer votre texte en tiddler (en rajoutant des double crochet la ou il en manque). Il suffit alors de cliquer sur le lien pour créer un nouveau tiddler avec un titre pré-initialisé. Il faut rajouter le tag UserStory
* Les mêmes principes sont utilisez pour créer les tâches
* N'oubliez pas chaque jours de faire le DailyScrum et de saisir le reste à faire du sprint en cours (ou bien dans [[Vue Générale]]
!Collaborez
Vous pouvez travailler a plusieurs sur un même ficher sans avoir a l'échanger via la messagerie ou le partager sur un répertoire commun.
Il faut pour cela le mettre en place sur un serveur.
# Créer le fichier [[store.php]] sur votre serveur, puis éditez sa liste d'utilisateurs
# Le TiddlyWiki risque d'être en lecture seule, il faut aller dans les options modifier &lt;&lt;option chkHttpReadOnly&gt;&gt; chkHttpReadOnly
# Il faut configurer dans le backstage / Upload les options de connection HomeParameters et/ou UploadOptions
# Si vous souhaité aller plus loin et utiliser la mise a jour directe de Tiddler (au lieu de sauver le fichier d'un bloc) il faut configurer UploadTiddler :  &lt;&lt;option chkUploadTiddler&gt;&gt; chkUploadTiddler.
# Si jamais vous souhaitez gérer plusieurs projets dans le même navigateur, sachez que les options de connections sont en principe stockées dans des cookies. Afin de bypasser ce mécanisme et de les stocker dans le fichier lui même [[zCookieJar]] a été créé. Les options sont en dur. ''Attention'', quand vous changez les paramètres de connection ils ne seront pas pris en compte a moins de faire un nouveau &quot;Bake cookies&quot; dans CookieManagerPlugin. Pour activer le [[zCookieJar]] il faut lui rajouter le tag systemConfig.
!En cas de problème
* Consultez la liste des [[Bugs]] connus
* Contactez le responsable du projet mailto:Y.%20BABEL&lt;ybabel@ideia.fr&gt;?subject=TiddlyWikiScrum
* Demandez de l'aide sur les mailing liste TiddlyWiki : http://groups.google.com/group/TiddlyWiki
!Hints&amp;Tips
!!Pour générer du CSV
//{{{
&lt;&lt;tiddlerList tags:task format:&quot;csv&quot; header:&quot;[ &quot; footer:&quot; ]&quot;&gt;&gt;
//}}}
&lt;&lt;&lt;
&lt;&lt;tiddlerList tags:task format:&quot;csv&quot; header:&quot;[ &quot; footer:&quot; ]&quot;&gt;&gt;
&lt;&lt;&lt;
!!Calculer la somme du reste a faire
//{{{
&lt;&lt;forEachTiddler
where 'tiddler.tags.contains(&quot;task&quot;)'
write
'&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j0&quot;)
+&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j1&quot;)
+&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j2&quot;)
+&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j3&quot;)+&quot;|\n&quot;'
begin '&quot;|!j0|!j1|!j2|!j3|\n&quot;' 
end '&quot;|&lt;&lt;columncalc sum 1 -1\&gt;\&gt;|&lt;&lt;columncalc sum 1 -1\&gt;\&gt;|&lt;&lt;columncalc sum 1 -1\&gt;\&gt;|&lt;&lt;columncalc sum 1 -1\&gt;\&gt;|\n&quot;'
&gt;&gt; 
//}}}
&lt;&lt;&lt;
&lt;&lt;forEachTiddler
where 'tiddler.tags.contains(&quot;task&quot;)'
write
'&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j0&quot;)
+&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j1&quot;)
+&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j2&quot;)
+&quot;|&quot;+store.getTiddlerSlice(tiddler.title,&quot;j3&quot;)+&quot;|\n&quot;'
begin '&quot;|!j0|!j1|!j2|!j3|\n&quot;' 
end '&quot;|! &lt;&lt;columncalc sum 1 -1\&gt;\&gt;|! &lt;&lt;columncalc sum 1 -1\&gt;\&gt;|! &lt;&lt;columncalc sum 1 -1\&gt;\&gt;|! &lt;&lt;columncalc sum 1 -1\&gt;\&gt;|\n&quot;'
&gt;&gt; 
&lt;&lt;&lt;
!!Modèle de données
* Le backlog contient la liste ''ordonnée'' de toutes les UserStory ordonnées
* Une UserStory se décompose en plusieurs tâches
* Un Sprint est une selection de UserStory ordonnées, et de leurs tâches</pre>
</div>
<div title="Aide pour KVM" modifier="YBA" created="200810151226" modified="200810301042" tags="doc" changecount="3">
<pre>mise en place du bridge : 
http://forum.ubuntu-fr.org/viewtopic.php?id=208161
https://help.ubuntu.com/8.04/serverguide/C/libvirt.html
http://howto.wikia.com/wiki/Howto_share_data_between_host_and_guest_OS_on_Qemu_with_Samba
https://help.ubuntu.com/community/KVM</pre>
</div>
<div title="Backlog" modifier="YBA" created="200809091558" modified="200809120932" tags="SortList" changecount="32" tdpcount="">
<pre>* troisième tache | 5 
* première tache | 13 
et puis une nouvelle idée a rajouter | 3
* deuxième tache | 1</pre>
</div>
<div title="BacklogViewTemplate" modifier="YBA" created="200809151410" modified="200809221447" tags="customTemplate" changecount="10">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;span class='help'&gt;Le backlog de produit est une liste de UserStory. Vous pouvez les saisirs ici en tant que texte, et les transformer en liens vers des tiddler (vides) avec le bouton Update. Ou bien si vous avez créé des UserStory d'une autre manière, vous pouvez rajouter les nouvelles entrées ici grâce au même bouton.&lt;/span&gt;
&lt;span macro='newTiddler label:&quot;new User Story&quot; text:{{store.getTiddlerText(&quot;EmptyUserStory&quot;)}} title:&quot;Titre court de UserStory&quot; tag:UserStory'&gt;
&lt;div class='viewer' macro='SortList fields:poids'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='PopulateUpdateList want:UserStory'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="BetterTimeLineMacro" modifier="YBA" created="200810280922" tags="systemConfig" changecount="1">
<pre>/***
|Name|BetterTimelineMacro|
|Created by|SaqImtiaz|
|Location|http://tw.lewcid.org/#BetterTimelineMacro|
|Version|0.5 beta|
|Requires|~TW2.x|
!!!Description:
A replacement for the core timeline macro that offers more features:
*list tiddlers with only specfic tag
*exclude tiddlers with a particular tag
*limit entries to any number of days, for example one week
*specify a start date for the timeline, only tiddlers after that date will be listed.

!!!Installation:
Copy the contents of this tiddler to your TW, tag with systemConfig, save and reload your TW.
Edit the ViewTemplate to add the fullscreen command to the toolbar.

!!!Syntax:
{{{&lt;&lt;timeline better:true&gt;&gt;}}}
''the param better:true enables the advanced features, without it you will get the old timeline behaviour.''

additonal params:
(use only the ones you want)
{{{&lt;&lt;timeline better:true  onlyTag:Tag1 excludeTag:Tag2 sortBy:modified/created firstDay:YYYYMMDD maxDays:7 maxEntries:30&gt;&gt;}}}

''explanation of syntax:''
onlyTag: only tiddlers with this tag will be listed. Default is to list all tiddlers.
excludeTag: tiddlers with this tag will not be listed.
sortBy: sort tiddlers by date modified or date created. Possible values are modified or created.
firstDay: useful for starting timeline from a specific date. Example: 20060701 for 1st of July, 2006
maxDays: limits timeline to include only tiddlers from the specified number of days. If you use a value of 7 for example, only tiddlers from the last 7 days will be listed.
maxEntries: limit the total number of entries in the timeline.


!!!History:
*28-07-06: ver 0.5 beta, first release

!!!Code
***/
//{{{
// Return the tiddlers as a sorted array
TiddlyWiki.prototype.getTiddlers = function(field,excludeTag,includeTag)
{
          var results = [];
          this.forEachTiddler(function(title,tiddler)
          {
          if(excludeTag == undefined || tiddler.tags.find(excludeTag) == null)
                        if(includeTag == undefined || tiddler.tags.find(includeTag)!=null)
                                      results.push(tiddler);
          });
          if(field)
                   results.sort(function (a,b) {if(a[field] == b[field]) return(0); else return (a[field] &lt; b[field]) ? -1 : +1; });
          return results;
}



//this function by Udo
function getParam(params, name, defaultValue)
{
          if (!params)
          return defaultValue;
          var p = params[0][name];
          return p ? p[0] : defaultValue;
}

window.old_timeline_handler= config.macros.timeline.handler;
config.macros.timeline.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
          var args = paramString.parseParams(&quot;list&quot;,null,true);
          var betterMode = getParam(args, &quot;better&quot;, &quot;false&quot;);
          if (betterMode == 'true')
          {
          var sortBy = getParam(args,&quot;sortBy&quot;,&quot;modified&quot;);
          var excludeTag = getParam(args,&quot;excludeTag&quot;,undefined);
          var includeTag = getParam(args,&quot;onlyTag&quot;,undefined);
          var tiddlers = store.getTiddlers(sortBy,excludeTag,includeTag);
          var firstDayParam = getParam(args,&quot;firstDay&quot;,undefined);
          var firstDay = (firstDayParam!=undefined)? firstDayParam: &quot;00010101&quot;;
          var lastDay = &quot;&quot;;
          var field= sortBy;
          var maxDaysParam = getParam(args,&quot;maxDays&quot;,undefined);
          var maxDays = (maxDaysParam!=undefined)? maxDaysParam*24*60*60*1000: (new Date()).getTime() ;
          var maxEntries = getParam(args,&quot;maxEntries&quot;,undefined);
          var last = (maxEntries!=undefined) ? tiddlers.length-Math.min(tiddlers.length,parseInt(maxEntries)) : 0;
          for(var t=tiddlers.length-1; t&gt;=last; t--)
                  {
                  var tiddler = tiddlers[t];
                  var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
                  if ((theDay&gt;=firstDay)&amp;&amp; (tiddler[field].getTime()&gt; (new Date()).getTime() - maxDays))
                     {
                     if(theDay != lastDay)
                               {
                               var theDateList = document.createElement(&quot;ul&quot;);
                               place.appendChild(theDateList);
                               createTiddlyElement(theDateList,&quot;li&quot;,null,&quot;listTitle&quot;,tiddler[field].formatString(this.dateFormat));
                               lastDay = theDay;
                               }
                  var theDateListItem = createTiddlyElement(theDateList,&quot;li&quot;,null,&quot;listLink&quot;,null);
                  theDateListItem.appendChild(createTiddlyLink(place,tiddler.title,true));
                  }
                  }
          }

          else
              {
              window.old_timeline_handler.apply(this,arguments);
              }
}
//}}}</pre>
</div>
<div title="Bugs" modifier="YBA" created="200809191319" changecount="1">
<pre>Bugs connus :
* les tiddlers qui ont une apostrophe ' dans leur titre ne sont pas synchronisés correctement par UploadTiddler
* les sprints qui ont un espace dans leur nom peuvent poser problème
* il y a un bug d'affichage du burndown chart quand on supprime une valeur au lieu de la mettre a 0 (car undefined &lt;&gt; null)
* il semble que le saisie des heures / tâches pose problème parfois, sans que le bug ne soit vraiment reproductible</pre>
</div>
<div title="Burdown" modifier="YBA" created="200809090812" modified="200809221225" changecount="30">
<pre>!Tous les sprints
&lt;&lt;sliceGrid edit inline [[tags:task]] slices:+SprintDays&gt;&gt;
&lt;&lt;histoGram [[tags:task ]] width:200 slices:+SprintDays&gt;&gt;
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;sprint&quot;)'
write '&quot;! [[&quot;+tiddler.title+&quot;]]\n&lt;&lt;sliceGrid edit inline [[tags:task &quot;+tiddler.title+&quot;]] slices:+SprintDays$))\n&lt;&lt;histoGram [[tags:task &quot;+tiddler.title+&quot;]] width:200 slices:+SprintDays$))\n&quot;'&gt;&gt;
</pre>
</div>
<div title="CahierChargeInTech" modifier="YBA" created="200810151220" modified="200903241646" tags="doc" changecount="4">
<pre>Cahier des charges pour étude montée en charge de Drupal.
EtapesLoadTestDrupal
! Grille d'éval
* Drupal réseau social, Drupal HForMen
* Avec ou sans cache, block cache, Drupal 6, APC
* MySQL ou PostGres
* Perte de perf avec panels, cck, views.
* Comparer views et hardcoding
* Réplication de base MySQL master-slave
* Architecture SQUID,
* Load balancer vers multiples serveurs
* Architecture virtualisé XEN/VirtualBox pour dupliquer les serveurs : clustering d'hyperviseurs
! Tester 
* APC (apache cache) http://drupal.org/project/apc
* avec le cache drupal activé ou non, Drupal 6 vs Drupal 5
* module panels, jquery, cck, views (tester la différence entre les 2)
* http://drupal.org/project/advcache
* http://drupal.org/project/blockcache
* ferme de serveur SQUID, architecture
* determiner les normes et l'architecture de base
* TPMC
! Webographie
* http://drupalfr.org/node/1391 - pb de session et de cache
* http://www.opensourcetesting.org/performance.php - liste d'outils open source
* http://drupal.org/simpletest
* http://drupal.org/project/loadtest
* http://groups.drupal.org/node/9893 - sharding : split de la base Drupal pour plus de perf - nouveau concept
* http://drupal.org/node/51567 - qq étapes pour optimizer Drupal
* http://shiflett.org/blog/2004/jul/php-scales - php scales well a cause du mod_php - mais le fait de relier a MySQL scale beaucoup moins.
* http://buytaert.net/drupal-performance - //It is not always easy to scale Drupal -- not because Drupal sucks, but simply because scaling the LAMP stack (including Drupal) takes no small amount of skill. You need to buy the right hardware, install load balancers, setup MySQL servers in master-slave mode, setup static file servers, setup web servers, get PHP working with an opcode cacher, tie in a distributed memory object caching system like memcached, integrate with a content delivery network, watch security advisories for every component in your system and configure and tune the hell out of everything//.
* ''http://drupal.org/node/2601'' server tuning
* http://2bits.com/articles/benchmarking-postgresql-vs-mysql-performance-using-drupal-5x.html
* ''http://drupal.org/node/79237'' Drupal performance benchmarking
* http://linuxfr.org/2003/04/21/12145.html
* http://linuxfr.org/2003/04/07/12001.html
* http://phpkitchen.com/2004/07/cmss-benchmarked/ juste un test informel sur un forum, qui met e107 et Drupal en premier.
* http://www.tpc.org/tpcc/results/tpcc_results.asp?print=false&amp;orderby=dbms&amp;sortby=asc
* http://www1.euro.dell.com/content/topics/topic.aspx/global/products/pedge/topics/en/benchmarks?c=fr&amp;cs=RC1078521&amp;l=fr&amp;s=pub&amp;~tab=2
* smb:/neptune/DEV/ORANGE/VALOF/Documents_Platon_Tous_Projets/Quels services de SGBDR pour quels besoins.pdf
* http://groups.drupal.org/high-performance
* http://drupal.org/node/26707 la future (en 2005) infrastructure Drupal
* ''http://drupal.org/node/163216'' Drupal infrastructure improvement : squid + memcached
* http://drupal.org/node/36347
* http://buytaert.net/scaling-with-mysql-replication : Drupal6 devrai permettre la scalabilité MySQL (master-slave)
* http://www.mysqlperformanceblog.com/tools/
* http://drupal.org/project/replication un module qui permettrai de faire la réplication comme préconisée par Dries... ''a tester''
* http://dev.mysql.com/doc/refman/5.0/en/replication-howto.html
* http://drupal.org/node/147160 un patch pour supporter la réplication de base, Drupal 7
* faire de la réplication en MySQL
* http://tabo.aurealsys.com/archives/2005/11/05/from-keyra-agustina-to-vampire-watermelons-the-wikipedia/ montre l'architecture wikipedia
* http://www.erigid.com/web_cluster.php un exemple de société qui fait de l'architecture scalable &quot;web cluster&quot;
* http://fr.iweb.com/hebergement-administre/web-cluster/ clustering et machine virtuelles
* ''http://buytaert.net/drupal-5-performance'' pour montrer comment on fait les tests de perfs
* http://buytaert.net/drupal-vs-joomla-performance
* ''http://buytaert.net/drupal-webserver-configurations-compared'' différents tests de perf avec Drupal
* ''http://www.johnandcailin.com/blog/john/scaling-drupal-open-source-infrastructure-high-traffic-drupal-sites'' les étapes avec des schémas !
* ''http://drupal.org/node/155850'' supers PDF sur l'architecture Drupal a mettre en place et tous les termes techniques
* http://www.nexen.net/actualites/tutorial/configuration_dun_cluster_apache_a_2_noeuds.php
* http://www.nexen.net/actualites/trucs/comment_configurer_un_cluster_mysql.php
* http://2bits.com/articles/drupal-performance-tuning-and-optimization-for-large-web-sites.html
* 12/08/08 http://books.tag1consulting.com/scalability a l'air sympa, a lire</pre>
</div>
<div title="ColumnCalculatorPlugin" modifier="YBA" created="200809091013" modified="200809220808" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|ColumnCalculatorPlugin|
|Source|http://www.TiddlyTools.com/#ColumnCalculatorPlugin|
|Version|0.6.1|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Description|calculate values from table cells in a column|
|Status| ALPHA - DO NOT DISTRIBUTE - USE AT YOUR OWN RISK |

!!!!!Usage
&lt;&lt;&lt;
{{{&lt;&lt;columncalc function startrow endrow&gt;&gt;}}}
where:
''function'' is a keyword:
* &quot;total&quot; or &quot;sum&quot; or //no param// - adds up numeric values for cells above it in the column
* &quot;count&quot; - gives number of cells in column
* &quot;average&quot; or &quot;avg&quot; - gives average of cells in column (i.e., total/count)
''startrow''/''endrow'' are optional, and specify a ONE-based range of rows for limiting the calculation.  Use negative numbers to specify an offset from the current row (e.g., {{{&lt;&lt;calc sum 3 5&gt;&gt;}}} adds up rows 3, 4 and 5, while {{{&lt;&lt;calc sum 1 -1&gt;&gt;}}} adds up all numbers in the column excluding the current row (i.e., the same as the default if no startrow/endrow params are specified)
&lt;&lt;&lt;
!!!!!Examples
&lt;&lt;&lt;
''with numeric values...''
{{{
| foo| 3.2 |
| bar| 1.1 |
| baz| 2.9 |
| gronk| 4.3 |
| snork| 5.5 |
| count| &lt;&lt;columncalc count 1 -1&gt;&gt; |
| total| &lt;&lt;columncalc sum 1 -2&gt;&gt; |
| avg| &lt;&lt;columncalc average 1 -3&gt;&gt; |
}}}
| foo| 3.2 |
| bar| 1.1 |
| baz| 2.9 |
| gronk| 4.3 |
| snork| 5.5 |
| count| &lt;&lt;columncalc count 1 -1&gt;&gt; |
| total| &lt;&lt;columncalc sum 1 -2&gt;&gt; |
| avg| &lt;&lt;columncalc average 1 -3&gt;&gt; |

''with time-formatted values (hh:mm:ss)...''
{{{
| foo| 00:22:15 |
| bar| 00:03:30 |
| baz| 00:01:45 |
| count| &lt;&lt;columncalc count 1 -1&gt;&gt; |
| total| &lt;&lt;columncalc sum 1 -2&gt;&gt; |
| avg| &lt;&lt;columncalc average 1 -3&gt;&gt; |
}}}
| foo| 00:22:15 |
| bar| 00:03:30 |
| baz| 00:01:45 |
| count| &lt;&lt;columncalc count 1 -1&gt;&gt; |
| total| &lt;&lt;columncalc sum 1 -2&gt;&gt; |
| avg| &lt;&lt;columncalc average 1 -3&gt;&gt; |
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2007.10.26 [0.6.1] in handler(), using &quot;.textContent&quot; instead of &quot;.innerHTML&quot; when reading values from table cells.  This allows use of values that are transcluded from slices in other tiddlers using the {{{&lt;&lt;tiddler &quot;TiddlerName::slicename&quot;&gt;&gt;}}} syntax.
2007.06.29 [0.6.0] added support for handling values in hh:mm:ss format
2007.04.02 [0.5.0] started
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.columncalc = {major: 0, minor: 6, revision: 1, date: new Date(2007,10,26)};
config.macros.columncalc= {
	handler:
	function(place,macroName,params,wikifier,paramString,tiddler) {
		if (place.parentNode.nodeName.toLowerCase()!=&quot;tr&quot;) return false; // not in a table
		var fn=params.shift(); // first param is function name
		var tbody=place.parentNode.parentNode;
		var row=tbody.childNodes.length-1; // current row #
		var col=place.parentNode.childNodes.length-1; // current column #
		var count=total=0;
		var startrow=0; var endrow=row-1;
		if (params[0]) var startrow=params.shift();
		if (startrow&lt;0) startrow=1*startrow+row; else startrow=startrow-1;
		if (params[0]) var endrow=params.shift();
		if (endrow&lt;0) endrow=1*endrow+row; else endrow=endrow-1;
		var calcTime=false;
		for (r=startrow; r&lt;=endrow; r++) {
			var cell=tbody.childNodes[r].childNodes[col].textContent;
			if (!cell) cell=tbody.childNodes[r].childNodes[col].innerHTML; // fallback for older browsers
			if (!isNaN(cell)) {
				total=total+eval(cell);
				count++;
			} else { // maybe an hh:mm:ss time value?
				var hms=cell.split(&quot;:&quot;); var h=hms[0]; var m=hms[1]; var s=hms[2];
				if (hms.length!=3 || isNaN(h) || isNaN(m) || isNaN(s)) continue;  // not a valid time value... skip
				var showTime=true; // found a time value... use time formatting for results.
				total=total+h*3600+m*60+s*1;
				count++;
			}
		}
		switch (fn) {
			case &quot;count&quot;:
				var result=count;
				break;
			case &quot;average&quot;:
			case &quot;avg&quot;:
				var result=Math.floor(total/count*100)/100; // truncate to two decimal places
				break;
			case &quot;total&quot;:
			case &quot;sum&quot;:
			default:
				var result=total;
				break;
		}
		if (showTime &amp;&amp; fn!=&quot;count&quot;) {
			var h=Math.floor(result/3600);
			var m=Math.floor((result-h*3600)/60);
			var s=Math.floor((result-h*3600-m*60)*100)/100; // truncate to two decimal places
			result=(h&lt;10?'0':'')+h+&quot;:&quot;+(m&lt;10?'0':'')+m+&quot;:&quot;+(s&lt;10?'0':'')+s;
		}
		createTiddlyText(place,result);
	}
}
//}}}</pre>
</div>
<div title="Communiqué Intech'Info" modifier="YBA" created="200903181444" modified="200903181459" changecount="3">
<pre>IDEIA renforce sa compétence Drupal en lançant, conjointement avec InTech'Info une étude sur les architectures hautes performances basées sur la virtualisation. 
--Appuyée par une équipe d'étudiants, IDEIA soutient la communauté Drupal en dirigeant la rédaction d'un rapport sur différentes architectures à même de délivrer un site web à grand traffic. Cette étude permettra de connaître les réactions du CMS face à la montée en charge.--
--Appuyée par une équipe d'étudiants, IDEIA contribue à la communauté Drupal en dirigeant la rédaction d'un rapport permettant de connaître les réactions du CMS face à la montée en charge.--
Au menu : virtualisation, répartition de charge, reverse proxy, réplication de bases, clustering, stress test, monitoring.
Différents modèles d'architecture seront testés et mis en relief afin de savoir comment répondre à la montée en charge des sites Drupal. Quelles sont les possibilités d'évolution, quelles sont leur coût, comment détecter les goulots d'étranglement et y remédier, à l'aide de quels outils. Le tout entièrement en OpenSource.
Le rapport sera rendu public et mis à disposition de la communauté.</pre>
</div>
<div title="CompareTiddlersPlugin" modifier="YBA" created="200710151510" modified="200809220800" tags="systemConfig excludeSearch excludeLists" creator="ELSDesignStudios" server.type="file" server.host="www.tiddlytools.com" server.page.revision="200710151656" changecount="1">
<pre>/***
|Name|CompareTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#CompareTiddlersPlugin|
|Version|1.0.0|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Description|show color-coded differences between two selected tiddlers|
!!!!!Usage
&lt;&lt;&lt;
{{{&lt;&lt;compareTiddlers&gt;&gt;}}}
{{smallform small{&lt;&lt;compareTiddlers&gt;&gt;}}}
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2007.10.15 [1.0.0] converted from inline script to true plugin
2006.12.27 [0.0.0] inline script.  {{{diff()}}} and {{{diffString()}}} functions written by Bradley Meck.
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.CompareTiddlers= {major: 1, minor: 0, revision: 0, date: new Date(2007,10,15)};
//}}}
//{{{
config.shadowTiddlers.CompareTiddlers=&quot;&lt;&lt;compareTiddlers&gt;&gt;&quot;;
//}}}
//{{{
config.macros.compareTiddlers= {
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		setStylesheet(this.css,&quot;CompareTiddlersStyles&quot;);
		var console=createTiddlyElement(place,&quot;span&quot;); console.innerHTML=this.html;
		var form=console.getElementsByTagName(&quot;form&quot;)[0];
		var target=form.nextSibling;
		this.reset(form,target);
	},
	css: &quot;.compareTiddlersResults \
		{ display:none;clear:both;margin-top:1em;border:1px solid;-moz-border-radius:1em;padding:1em;white-space:normal; }&quot;,
	html: &quot;&lt;form&gt;&lt;!-- \
		--&gt;&lt;select name=list1 size=1 style='width:30%' \
			onchange='config.macros.compareTiddlers.pick(this,this.form.view1,this.form.edit1,this.form.text1)'&gt;&lt;/select&gt;&lt;!-- \
		--&gt;&lt;input type=button name=view1 style='width:10%' value='view' disabled \
			onclick='if (this.form.list1.value.length) \
				story.displayTiddler(story.findContainingTiddler(this),this.form.list1.value,DEFAULT_VIEW_TEMPLATE)'&gt;&lt;!-- \
		--&gt;&lt;input type=button name=edit1 style='width:10%' value='edit' disabled \
			onclick='if (this.form.list1.value.length) \
				story.displayTiddler(story.findContainingTiddler(this),this.form.list1.value,DEFAULT_EDIT_TEMPLATE)'&gt;&lt;!-- \
		--&gt;&lt;select name=list2 size=1 style='width:30%' \
			onchange='config.macros.compareTiddlers.pick(this,this.form.view2,this.form.edit2,this.form.text2)'&gt;&lt;/select&gt;&lt;!-- \
		--&gt;&lt;input type=button name=view2 style='width:10%' value='view' disabled \
			onclick='if (this.form.list2.value.length) \
				story.displayTiddler(story.findContainingTiddler(this),this.form.list2.value,DEFAULT_VIEW_TEMPLATE)'&gt;&lt;!-- \
		--&gt;&lt;input type=button name=edit2 style='width:10%' value='edit' disabled \
			onclick='if (this.form.list2.value.length) \
				story.displayTiddler(story.findContainingTiddler(this),this.form.list2.value,DEFAULT_EDIT_TEMPLATE)'&gt;&lt;br&gt;&lt;!-- \
		--&gt;&lt;nobr&gt;&lt;!-- \
		--&gt;&lt;textarea name=text1 style='width:49.5%;display:none' rows='10' readonly&gt;&lt;/textarea&gt;&lt;!-- \
		--&gt;&lt;textarea name=text2 style='width:49.5%;display:none' rows='10' readonly&gt;&lt;/textarea&gt;&lt;!-- \
		--&gt;&lt;/nobr&gt;&lt;!-- \
		--&gt;&lt;div style='float:left'&gt;&lt;!-- \
		--&gt;Additions are shown in &lt;span style='color:green'&gt;GREEN&lt;/span&gt;, &lt;!-- \
		--&gt;deletions are shown in &lt;span style='color:red'&gt;RED&lt;/span&gt;&lt;!-- \
		--&gt;&lt;/div&gt;&lt;!-- \
		--&gt;&lt;div style='text-align:right'&gt;&lt;!-- \
		--&gt;&lt;input type=button name=compare style='width:10%' value='compare' disabled \
			onclick='config.macros.compareTiddlers.compare(this.form,this.form.nextSibling)'&gt;&lt;!-- \
		--&gt;&lt;input type=button name=done style='width:10%' value='done' disabled \
			onclick='config.macros.compareTiddlers.reset(this.form,this.form.nextSibling)'&gt;&lt;!-- \
		--&gt;&lt;/div&gt;&lt;!-- \
		--&gt;&lt;/form&gt;&lt;div class='compareTiddlersResults'&gt;contents to be replaced by results of comparison&lt;/div&gt; \
	&quot;,
	reset: function(f,target) {
		var tids=store.getTiddlers('title','excludeLists');
		f.text1.style.display=&quot;none&quot;; f.text1.value=&quot;&quot;;
		while (f.list1.options[0]) f.list1.options[0]=null; 
		f.list1.options[0]=new Option(&quot;select a tiddler...&quot;,&quot;&quot;,false,false);
		for (i=0; i&lt;tids.length; i++)
			f.list1.options[f.list1.length]=new Option(tids[i].title,tids[i].title,false,false);
		f.text2.style.display=&quot;none&quot;; f.text2.value=&quot;&quot;;
		while (f.list2.options[0]) f.list2.options[0]=null; 
		f.list2.options[0]=new Option(&quot;select a tiddler...&quot;,&quot;&quot;,false,false);
		for (i=0; i&lt;tids.length; i++)
			f.list2.options[f.list2.length]=new Option(tids[i].title,tids[i].title,false,false);
		f.view1.disabled=f.view2.disabled=f.edit1.disabled=f.edit2.disabled=f.compare.disabled=f.done.disabled=true;
		target.style.display=&quot;none&quot;;
		removeChildren(target);
	},
	pick: function(list,view,edit,text) {
		var f=list.form;
		view.disabled=edit.disabled=f.done.disabled=!list.value.length;
		f.compare.disabled=!f.list1.value.length||!f.list2.value.length;
		if (!list.value.length) return;
		f.text1.style.display=f.text2.style.display=&quot;inline&quot;;
		text.value=store.getTiddlerText(list.value);
	},
	compare: function(f,target) {
		if (!f.list1.value.length) { f.list1.focus(); return alert(&quot;select a tiddler&quot;); }
		var t1=store.getTiddlerText(f.list1.value); if (!t1) { displayMessage(f.list1.value+&quot; not found&quot;);return false; }
		if (!f.list2.value.length) { f.list2.focus(); return alert(&quot;select a tiddler&quot;); }
		var t2=store.getTiddlerText(f.list2.value); if (!t2) { displayMessage(f.list2.value+&quot; not found&quot;);return false; }
		var out=this.diffString(t1,t2); if (!out || !out.length) out=&quot;no differences&quot;;
		removeChildren(target);
		target.innerHTML=out;
		target.style.display=&quot;block&quot;;
		f.done.disabled=false;
	},
	diffString: function( o, n ) {
		// This function was written by Bradley Meck
		// returns difference between old and new text, color-formatted additions and deletions
		if (o==n) return &quot;&quot;; // simple check, saves time if true
		var error = 5;
		var reg = new RegExp( &quot;\\n|(?:.{0,&quot;+error+&quot;})&quot;, &quot;g&quot; );
		var oarr = o.match( reg ); // dices text into chunks
		var narr = n.match( reg );
		var out = this.diff(oarr,narr); // compare the word arrays
		var str = &quot;&quot;; // construct output
		for (i=0; i&lt;out.length; i++) {
			switch (out[i].change) {
				case &quot;ADDED&quot;:
					str+=&quot;&lt;span style='color:green'&gt;&quot;;
					str+=narr.slice(out[i].index,out[i].index+out[i].length).join(&quot;&quot;);
					str+=&quot;&lt;/span&gt; &quot;;
					break;
				case &quot;DELETED&quot;:
					str+=&quot;&lt;span style='color:red'&gt;&quot;;
					str+=oarr.slice(out[i].index,out[i].index+out[i].length).join(&quot;&quot;);
					str+=&quot;&lt;/span&gt; &quot;;
					break;
				default:
					str+=&quot;&lt;span&gt;&quot;;
					str+=oarr.slice(out[i].index,out[i].index+out[i].length).join(&quot;&quot;);
					str+=&quot;&lt;/span&gt; &quot;;
					break;
			}	
		}
		return str;
	},
	diff: function( oldArray, newArray ) {
		// This function was written by Bradley Meck
		// finds the differences between one set of objects and another.
		// The objects do not need to be Strings.  It outputs an array of objects with the properties value and change.
		// This function is pretty hefty but appears to be rather light for a diff and tops out at O(N^2) for absolute worst cast scenario.
		var newElementHash = { };
		for( var i = 0; i &lt; newArray.length; i++ ) {
			if( ! newElementHash [ newArray [ i ] ] ) {
				newElementHash [ newArray [ i ] ] = [ ];
			}
			newElementHash [ newArray [ i ] ].push( i );
		}
		var substringTable = [ ];
		for( var i = 0; i &lt; oldArray.length; i++ ) {
			if(newElementHash [ oldArray [ i ] ] ) {
				var locations = newElementHash [ oldArray [ i ] ] ;
				for( var j = 0; j &lt; locations.length; j++){
					var length = 1;
					while( i + length &lt; oldArray.length &amp;&amp; locations [ j ] + length &lt; newArray.length
						&amp;&amp; oldArray [ i + length ] == newArray [ locations [ j ] + length ] ){
						length++;
					}
					substringTable.push( {
						oldArrayIndex : i,
						newArrayIndex : locations [ j ],
						matchLength : length
					} );
				}
			}
		}
		substringTable.sort( function( a, b ) {
			if ( a.matchLength &gt; b.matchLength /* a is less than b by some ordering criterion */ ) {
				return -1;
			}
			if ( a.matchLength &lt; b.matchLength /* a is greater than b by the ordering criterion */ ) {
				return 1;
			}
			// a must be equal to b
			return 0
		} );
		//displayMessage( substringTable.toSource( ) );
		for( var i = 0; i &lt; substringTable.length; i++) {
			for( var j = 0; j &lt; i; j++) {
				var oldDelta = substringTable [ i ].oldArrayIndex + substringTable [ i ].matchLength - 1 - substringTable [ j ].oldArrayIndex;
				var newDelta = substringTable [ i ].newArrayIndex + substringTable [ i ].matchLength - 1 - substringTable [ j ].newArrayIndex;
				//displayMessage( &quot;oldDelta ::: &quot; + oldDelta );
				//displayMessage( &quot;newDelta ::: &quot; + newDelta );
				//displayMessage( &quot;matchLength ::: &quot; + substringTable [ j ].matchLength );
				if( ( oldDelta &gt;= 0 &amp;&amp; oldDelta &lt;= substringTable [ j ].matchLength )
				|| ( newDelta &gt;= 0 &amp;&amp; newDelta &lt;= substringTable [ j ].matchLength )
				|| ( oldDelta &lt; 0 &amp;&amp; newDelta &gt; 0 )
				|| ( oldDelta &gt; 0 &amp;&amp; newDelta &lt; 0 ) ) {
					substringTable.splice( i, 1 );
					i--;
					break;
				}
			}
		}
		//displayMessage( substringTable.toSource(  ) );
		substringTable.sort( function( a, b ) {
			if ( a.oldArrayIndex &lt; b.oldArrayIndex /* a is less than b by some ordering criterion */ ) {
				return -1;
			}
			if ( a.oldArrayIndex &gt; b.oldArrayIndex /* a is greater than b by the ordering criterion */ ) {
				return 1;
			}
			// a must be equal to b
			return 0
		} );
		//displayMessage( substringTable.toSource( ) );
		var oldArrayIndex = 0;
		var newArrayIndex = 0;
		var results = [ ];
		for( var i = 0; i &lt; substringTable.length; i++ ) {
			if( oldArrayIndex != substringTable [ i ].oldArrayIndex ) {
				results.push( {
					change : &quot;DELETED&quot;,
					length : substringTable [ i ].oldArrayIndex - oldArrayIndex,
					index : oldArrayIndex
				} );
			}
			if( newArrayIndex != substringTable [ i ].newArrayIndex ) {
				results.push( {
					change : &quot;ADDED&quot;,
					length : substringTable [ i ].newArrayIndex - newArrayIndex,
					index : newArrayIndex
				} );
			}
			results.push( {
				change : &quot;STAYED&quot;,
				length : substringTable [ i ].matchLength,
				index : substringTable [ i ].oldArrayIndex
			} );
			oldArrayIndex = substringTable [ i ].oldArrayIndex + substringTable [ i ].matchLength;
			newArrayIndex = substringTable [ i ].newArrayIndex + substringTable [ i ].matchLength;
		}
		if( oldArrayIndex != oldArray.length ) {
			results.push( {
				change : &quot;DELETED&quot;,
				length : oldArray.length - oldArrayIndex,
				index : oldArrayIndex
			} );
		}
		if( newArrayIndex != newArray.length ) {
			results.push( {
				change : &quot;ADDED&quot;,
				length : newArray.length - newArrayIndex,
				index : newArrayIndex
			} );
		}
		return results;
	}
}
//}}}</pre>
</div>
<div title="Concepts scrum" modifier="YBA" created="200807211251" modified="200807211448" changecount="2">
<pre>doc : backlog de produit &gt; backlog de sprint &gt; user story &gt; critères d'acceptation &gt; taches
timeline : sprint &gt; planification &gt; mélée &gt;  burdown &gt; revue</pre>
</div>
<div title="Configuration" modifier="YBA" created="200809161536" modified="200809231306" changecount="18">
<pre>Default config
* SiteTitle &amp; SiteSubtitle
* MainMenu c'est le menu de gauche
* DefaultTiddlers, ce sont les tiddlers qui s'affichent au démarrage
* SideBarOptions, c'est la barre a droite des bouttons de création 
Templates
* SprintViewTemplate
* TaskEditTemplate / TaskViewTemplate
* BacklogViewTemplate
* UserStoryViewTemplate / UserStoryEditTemplate / UserStoryFormTemplate
* SortListViewTemplate
Misceleaneous
* SprintDays
* ToolbarCommands
* UploadTiddler @@chkUploadTiddler option: &lt;&lt;option chkUploadTiddler&gt;&gt;@@ @@chkUploadTiddlerFromFile option: &lt;&lt;option chkUploadTiddlerFromFile&gt;&gt;@@ @@txtUploadTiddlerStoreUrl: &lt;&lt;option txtUploadTiddlerStoreUrl&gt;&gt;@@
* [[zCookieJar]]
* HomeParameters
* UploadOptions
* [[sprint]] / [[task]] / UserStory / [[deprecated]]
* ExportTiddlerPlugin</pre>
</div>
<div title="CookieManagerPlugin" modifier="YBA" created="200809171402" modified="200809220808" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|CookieManagerPlugin|
|Source|http://www.TiddlyTools.com/#CookieManagerPlugin|
|Version|2.3.2|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Options|##Configuration|
|Description|view/add/delete browser-based cookies and &quot;bake&quot; cookies to CookieJar tiddler for 'sticky' settings|
!!!!!Usage
&lt;&lt;&lt;
This plugin provides an interactive control panel that lets you select, view, modify, or delete any of the current values for TiddlyWiki options that have been stored as local, private, //browser cookies//.  You can also use the control panel to &quot;bake cookies&quot;, which generates a set of javascript statements that assign hard-coded option values to the TiddlyWiki internal variables that correspond to the current browser cookie settings.  These hard-coded values are then stored in the [[CookieJar]] tiddler, which is tagged with&lt;&lt;tag systemConfig&gt;&gt; so that each time the document is loaded, the baked cookie settings will be automatically applied.

When a set of baked cookies is added to the [[CookieJar]], it is automatically surrounded by a conditional test so that the hard-coded settings will only be applied for the username that was in effect when they were initially generated.  In this way, if you publish or share your document with others, //your// particular baked cookie settings are not automatically applied to others, so that their own browser-based cookie settings (if defined) will be applied as usual.

Whenever you &quot;bake cookies&quot;, new hard-coded javascript assignment statements are *appended* to the end of the [[CookieJar]].  However, any baked cookies that were previously generated and stored in the [[CookieJar]] are not automatically removed from the tiddler.  As a result, because the most recently baked cookie settings in the [[CookieJar]] are always the last to be processed, the values assigned by older baked cookies are immediately overridden by the values from the newest baked cookies, so that the newest values will be in effect when the CookieJar startup processing is completed.

Each time you bake a new batch of cookies, it is recommended that you should review and hand-edit the [[CookieJar]] to remove any &quot;stale cookies&quot; or merge the old and new sets of baked cookies into a single block to simplify readability (as well as saving a little tiddler storage space).  Of course, you can also hand-edit the [[CookieJar]] tiddler at any time simply to remove a few individual //baked cookies// if they are no longer needed, and you can even delete the entire [[CookieJar]] tiddler and start fresh, if that is appropriate.  Please note that changing or deleting a baked cookie does not alter the current value of the corresponding option setting, and any changes you make to the [[CookieJar]] will only be applied after you have saved and reloaded the document in your browser.
&lt;&lt;&lt;
!!!!!Examples
&lt;&lt;&lt;
{{{&lt;&lt;cookieManager&gt;&gt;}}}
{{smallform small center{
@@display:block;width:35em;&lt;&lt;cookieManager&gt;&gt;@@}}}
&lt;&lt;&lt;
!!!!!Configuration
&lt;&lt;&lt;
&lt;&lt;option chkAllowBrowserCookies&gt;&gt; store ~TiddlyWiki option settings using private browser cookies
&lt;&lt;option chkMonitorBrowserCookies&gt;&gt; monitor browser cookie activity (show a message whenever a cookie is set or deleted)
&lt;&lt;option chkCookieManagerAddToAdvancedOptions&gt;&gt; display [[CookieManager]] in [[AdvancedOptions]]
//note: this setting does not take effect until you reload the document//
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.09.14 [2.3.2] fixed handling for blocked cookies (was still allowing some blocked cookies to be set)
2008.09.12 [2.3.1] added blocked[] array and allowBrowserCookie() test function for selective blocking of changes to browser cookies based on cookie name
2008.09.08 [2.3.0] extensive code cleanup: defined removeOptionCookie(), renamed cookies, added 'button' param for stand-alone &quot;bake cookies&quot; button, improved init of shadow [[CookieManager]] and [[CookieJar]] tiddlers for compatibility with new [[CookieSaverPlugin]]. 
2008.07.11 [2.2.1] fixed recursion error in hijack for saveOptionCookie()
2008.06.26 [2.2.0] added chkCookieManagerNoNewCookies option
2008.06.05 [2.1.3] replaced hard-coded definition for &quot;CookieJar&quot; title with option variable
2008.05.12 [2.1.2] add &quot;cookies&quot; task to backstage (moved from BackstageTasks)
2008.04.09 [2.1.0] added options: chkCookieManagerAddToAdvancedOptions
2008.04.08 [2.0.1] automatically include CookieManager control panel in AdvancedOptions shadow tiddler
2007.08.02 [2.0.0] converted from inline script
2007.04.29 [1.0.0] initial release
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.CookieManagerPlugin= {major: 2, minor: 3, revision: 1, date: new Date(2008,9,12)};
//}}}
//{{{
config.macros.cookieManager = {
	target:
		config.options.txtCookieJar||&quot;CookieJar&quot;,
	blockedCookies:
		[],
	allowBrowserCookie: function(name) {
		return true;
	},
	displayStatus: function(msg) {
		if (config.options.chkMonitorBrowserCookies &amp;&amp; !startingUp)
			displayMessage(&quot;CookieManager: &quot;+msg);
	},
	init: function() {
		if (config.options.txtCookieJar===undefined)
			config.options.txtCookieJar=this.target;
		if (config.options.chkAllowBrowserCookies===undefined)
			config.options.chkAllowBrowserCookies=true;
		if (config.options.chkMonitorBrowserCookies===undefined)
			config.options.chkMonitorBrowserCookies=false;

		config.shadowTiddlers.CookieManager=
			 &quot;/***\n&quot;
			+&quot;!!![[Browser cookies:|CookieManagerPlugin]] &quot;
			+&quot;{{fine{&lt;&lt;option chkAllowBrowserCookies&gt;&gt;enable &lt;&lt;option chkMonitorBrowserCookies&gt;&gt;monitor}}}\n&quot;
			+&quot;^^Review, modify, or delete browser cookies...&quot;
			+&quot;To block specific cookies, see [[CookieManagerPluginConfig]].^^\n&quot;
			+&quot;@@display:block;width:30em;{{smallform small{\n&lt;&lt;cookieManager&gt;&gt;}}}@@\n&quot;
			+&quot;***/\n&quot;;

		// add CookieManager to shadow CookieJar
		var h=&quot;/***\n&lt;&lt;tiddler CookieManager&gt;&gt;\n***/\n&quot;;
		var t=(config.shadowTiddlers[this.target]||&quot;&quot;).replace(new RegExp(h.replace(/\*/g,'\\*'),''),'')
		config.shadowTiddlers[this.target]=h+t;

		if (config.options.chkCookieManagerAddToAdvancedOptions===undefined)
			config.options.chkCookieManagerAddToAdvancedOptions=true;
		if (config.options.chkCookieManagerAddToAdvancedOptions)
			config.shadowTiddlers.AdvancedOptions+=&quot;\n!!CookieManager\n&gt;&lt;&lt;tiddler CookieManager&gt;&gt;&quot;;

		// add &quot;cookies&quot; backstage task
		if (config.tasks &amp;&amp; !config.tasks.cookies) { // for TW2.2b3 or above
			config.tasks.cookies = {
				text: &quot;cookies&quot;,
				tooltip: &quot;manage cookie-based option settings&quot;,
				content: &quot;{{groupbox{&lt;&lt;tiddler CookieManager&gt;&gt;&lt;&lt;tiddler [[&quot;+this.target+&quot;]]&gt;&gt;}}}&quot;
			}
			config.backstageTasks.push(&quot;cookies&quot;);
		}
	},
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		var span=createTiddlyElement(place,&quot;span&quot;);
		span.innerHTML=(params[0]&amp;&amp;params[0].toLowerCase()==&quot;button&quot;)?this.button:this.panel;
		this.setList(span.firstChild.list);
	},
	panel: '&lt;form style=&quot;display:inline;margin:0;padding:0&quot; onsubmit=&quot;return false&quot;&gt;&lt;!--\
		--&gt;&lt;select style=&quot;width:99%&quot; name=&quot;list&quot; \
			onchange=&quot;this.form.val.value=this.value.length?config.options[this.value]:\'\';&quot;&gt;&lt;!--\
		--&gt;&lt;/select&gt;&lt;br&gt;\
		&lt;input type=&quot;text&quot; style=&quot;width:98%;margin:0;&quot; name=&quot;val&quot; title=&quot;enter an option value&quot;&gt;&lt;br&gt;\
		&lt;input type=&quot;button&quot; style=&quot;width:33%;margin:0;&quot; value=&quot;get&quot; title=&quot;refresh list&quot; \
			onclick=&quot;config.macros.cookieManager.setList(this.form.list);&quot;&gt;&lt;!--\
		--&gt;&lt;input type=&quot;button&quot; style=&quot;width:33%;margin:0;&quot; value=&quot;set&quot; title=&quot;save cookie value&quot; \
			onclick=&quot;var cmc=config.macros.cookieManager;\
				var opt=this.form.list.value; var v=this.form.val.value; \
				var msg=opt+\' is a blocked cookie.  OK to proceed?\';\
				if ((!cmc.blockedCookies.contains(opt) &amp;&amp; cmc.allowBrowserCookie(opt))||confirm(msg)) {\
					config.options[opt]=opt.substr(0,3)==\'txt\'?v:(v.toLowerCase()==\'true\'); \
					saveOptionCookie(opt);config.macros.cookieManager.setList(this.form.list);\
				}&quot;&gt;&lt;!--\
		--&gt;&lt;input type=&quot;button&quot; style=&quot;width:33%;margin:0;&quot; value=&quot;del&quot; title=&quot;remove cookie&quot; \
			onclick=&quot;var cmc=config.macros.cookieManager; var opt=this.form.list.value; \
				var msg=opt+\' is a blocked cookie.  OK to proceed?\';\
				if ((!cmc.blockedCookies.contains(opt) &amp;&amp; cmc.allowBrowserCookie(opt))||confirm(msg)) {\
					removeOptionCookie(this.form.list.value,true); \
					cmc.setList(this.form.list);\
				}&quot;&gt;&lt;br&gt;\
		&lt;input type=&quot;button&quot; style=&quot;width:50%;margin:0;&quot; value=&quot;bake cookies&quot; \
			title=&quot;save current cookie-based option values into a tiddler&quot; \
			onclick=&quot;return config.macros.cookieManager.bake(this,false)&quot;&gt;&lt;!--\
		--&gt;&lt;input type=&quot;button&quot; style=&quot;width:50%;margin:0;&quot; value=&quot;bake all options&quot; \
			title=&quot;save ALL option values (including NON-COOKIE values) into a tiddler&quot; \
			onclick=&quot;return config.macros.cookieManager.bake(this,true)&quot;&gt;&lt;!--\
		--&gt;&lt;/form&gt;\
	',
	button: '&lt;form style=&quot;display:inline;margin:0;padding:0&quot; onsubmit=&quot;return false&quot;&gt;&lt;!--\
		--&gt;&lt;input type=&quot;button&quot; style=&quot;margin:0;&quot; value=&quot;bake cookies&quot; \
			title=&quot;save current browser-based cookie values into a tiddler&quot; \
			onclick=&quot;return config.macros.cookieManager.bake(this,false)&quot;&gt;&lt;!--\
		--&gt;&lt;/form&gt;\
	',
	getCookieList: function() {
		var cookies = { };
		if (document.cookie != &quot;&quot;) {
			var p = document.cookie.split(&quot;; &quot;);
			for (var i=0; i &lt; p.length; i++) {
				var pos=p[i].indexOf(&quot;=&quot;);
				if (pos==-1) cookies[p[i]]=&quot;&quot;;
				else cookies[p[i].substr(0,pos)]=unescape(p[i].slice(pos+1));
			}
		}
		var opt=new Array(); for (var i in config.options) if (cookies[i]) opt.push(i); opt.sort();
		return opt;
	},
	setList: function(list) {
		if (!list) return false;
		var opt=this.getCookieList();
		var sel=list.selectedIndex;
		while (list.options.length &gt; 1) { list.options[1]=null; } // clear list (except for header item)
		list.options[0]=new Option(&quot;There are &quot;+opt.length+&quot; cookies...&quot;,&quot;&quot;,false,false);
		if (!opt.length) { list.form.val.value=&quot;&quot;; return; } // no cookies
		var c=1;
		for(var i=0; i&lt;opt.length; i++) {
			var txt=&quot;&quot;;
			if  (opt[i].substr(0,3)==&quot;chk&quot;)
				txt+=&quot;[&quot;+(config.options[opt[i]]?&quot;x&quot;:&quot;_&quot;)+&quot;] &quot;;
			txt+=opt[i];
			list.options[c++]=new Option(txt,opt[i],false,false);
		}
		list.selectedIndex=sel&gt;0?sel:0;
		list.form.val.value=sel&gt;0?config.options[list.options[sel].value]:&quot;&quot;;
	},
	header:
		&quot;/***\n&quot;
		+&quot;!!![[Baked cookies:|CookieManagerPlugin]]\n&quot;
		+&quot;^^Press {{smallform{&lt;&lt;cookieManager button&gt;&gt;}}} to save the current browser cookies... &quot;
		+&quot;then hand-edit this section to customize the results.^^\n&quot;
		+&quot;***/\n&quot;,
	format: function(name) {
		if (name.substr(0,3)=='chk')
			return '\tconfig.options.'+name+'='+(config.options[name]?'true;':'false;');
		return '\tconfig.options.'+name+'=&quot;'+config.options[name]+'&quot;;';
	},
	bake: function(here,all) {
		if (story.isDirty(this.target)) return false; // target is being hand-edited... do nothing
		var text=store.getTiddlerText(this.target);
		if (text.indexOf(this.header)==-1) {
			text+=this.header;
			displayMessage(&quot;CookieManager: added 'Baked Cookies' section to CookieJar&quot;);
		}
		var who=config.options.txtUserName;
		var when=new Date();
		var tags=['systemConfig'];
		var tid=store.getTiddler(this.target)||store.saveTiddler(this.target,this.target,text,who,when,tags,{});
		if (!tid) return false; // if no target... do nothing
		if (all) { 
			var opts=new Array();
			for (var i in config.options) if (i.substr(0,3)=='chk'||i.substr(0,3)=='txt') opts.push(i);
			opts.sort();
		}
		else var opts=this.getCookieList();
		var t=tid.text;
		if (t.indexOf(this.header)==-1) t+=this.header;
		t+='\n// '+opts.length+(all?' options':' cookies')+' saved ';
		t+=when.formatString('on DDD, MMM DDth YYYY at 0hh:0mm:0ss');
		t+=' by '+who+'//\n';
		t+='//^^(edit/remove username check and/or individual option settings as desired)^^//\n';
		t+='//{{{\n';
		t+='if (config.options.txtUserName==&quot;'+who+'&quot;) {\n';
		for (i=0; i&lt;opts.length; i++) t+=config.macros.cookieManager.format(opts[i])+&quot;\n&quot;;
		t+='}\n//}}}\n';
		store.saveTiddler(this.target,this.target,t,who,when,tags,tid?tid.fields:{});
		story.displayTiddler(story.findContainingTiddler(this),this.target);
		story.refreshTiddler(this.target,null,true);
		var msg=opts.length+(all?' options':' cookies')+' have been saved in '+this.target+'.  ';
		msg+='Please review all stored settings.';
		displayMessage(msg);
		return false;
	}
}
//}}}
//{{{
// Hijack saveOptionCookie() to add cookie blocking and monitoring messages
config.macros.cookieManager.saveOptionCookie=saveOptionCookie;
window.saveOptionCookie=function(name,force)
{
	var cmc=config.macros.cookieManager; // abbrev
	if (force || ((config.options.chkAllowBrowserCookies || name==&quot;chkAllowBrowserCookies&quot;)
		&amp;&amp; !cmc.blockedCookies.contains(name) &amp;&amp; cmc.allowBrowserCookie(name))) {
		cmc.saveOptionCookie.apply(this,arguments);
		cmc.displayStatus(name+&quot;=&quot;+config.options[name]);
	} else cmc.displayStatus(&quot;setting of '&quot;+name+&quot;' is blocked&quot;);
}

// if removeOptionCookie() function is not defined by TW core, define it here.
if (window.removeOptionCookie===undefined) {
	window.removeOptionCookie=function(cookie) {
		var ex=new Date(); ex.setTime(ex.getTime()-1000);  // immediately expire cookie
		document.cookie = cookie+&quot;=novalue; path=/; expires=&quot;+ex.toGMTString();
	}
}

// ... and then hijack it to add cookie blocking and monitoring messages
config.macros.cookieManager.removeOptionCookie=removeOptionCookie;
window.removeOptionCookie=function(name,force)
{
	var cmc=config.macros.cookieManager; // abbrev
	if (!cmc.getCookieList().contains(name))
		return; // not a current cookie!
	if (force || ((config.options.chkAllowBrowserCookies || name==&quot;chkAllowBrowserCookies&quot;)
		&amp;&amp; !cmc.blockedCookies.contains(name) &amp;&amp; cmc.allowBrowserCookie(name))) {
		cmc.removeOptionCookie.apply(this,arguments);
		cmc.displayStatus(&quot;deleted &quot;+name);
	} else cmc.displayStatus(&quot;deletion of '&quot;+name+&quot;' is blocked&quot;);
}
//}}}</pre>
</div>
<div title="CookieManagerPluginConfig" modifier="YBA" created="200809171425" modified="200809220808" tags="systemConfig excludeSearch excludeLists" changecount="5">
<pre>/***
|Name|CookieManagerPluginConfig|
|Source|http://www.TiddlyTools.com/#CookieManagerPluginConfig|
|Requires|CookieManagerPlugin|
|Description|custom settings for [[CookieManagerPlugin]]|
!!!!!Browser Cookie Configuration:
***/
// // &lt;&lt;option chkAllowBrowserCookies&gt;&gt; store ~TiddlyWiki option settings using private browser cookies
// // &lt;&lt;option chkMonitorBrowserCookies&gt;&gt; monitor browser cookie activity (shows a message whenever a cookie is updated)
//{{{
// default settings:
config.options.chkAllowBrowserCookies=false;	// if FALSE, this blocks *all* cookies
config.options.chkMonitorBrowserCookies=false;
//}}}

// // Individual cookie names can be prevented from being created, modified, or deleted in your browser's stored cookies by adding them to the {{{config.macros.cookieManager.blockedCookies}}} array: 
//{{{
var bc=config.macros.cookieManager.blockedCookies; // abbreviation
bc.push(&quot;chkBackstage&quot;);		// TiddlyWiki:  core
bc.push(&quot;txtMainTab&quot;);			// TiddlyWiki:  SideBarTabs
bc.push(&quot;txtTOCSortBy&quot;);		// TiddlyTools: TableOfContentsPlugin
bc.push(&quot;txtCatalogTab&quot;);		// TiddlyTools: ShowCatalog
bc.push(&quot;txtUploadFilename&quot;);		// BidiX: UploadPlugin
bc.push(&quot;txtUploadDir&quot;);		// BidiX: UploadPlugin
bc.push(&quot;pasPassword&quot;);			// BidiX: UploadPlugin
bc.push(&quot;pasUploadPassword&quot;);		// BidiX: UploadPlugin
//}}}
// // You can also define a javascript test function that determines whether or not any particular cookie name should be blocked or allowed.  The {{{config.macros.cookieManager.allowBrowserCookie()}}} function should return FALSE if the browser cookie should be blocked, or TRUE if changes to the cookie should be allowed:
//{{{
config.macros.cookieManager.allowBrowserCookie=function(name) {
	// add tests based on specific cookie names and runtime conditions
	return true;
}
//}}}</pre>
</div>
<div title="CoreTweaks" modifier="YBA" created="200809151454" modified="200809220801" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|CoreTweaks|
|Source|http://www.TiddlyTools.com/#CoreTweaks|
|Version|n/a|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.2.0|
|Type|plugin|
|Requires||
|Overrides|various|
|Description|a small collection of overrides to TW core functions|
This tiddler contains some quick tweaks and modifications to TW core functions to provide minor changes in standard features or behavior.  It is hoped that some of these tweaks may be incorporated into later versions of the TW core, so that these adjustments will be available without needing these add-on definitions.
&gt;''Note: the changes contained in this tiddler are generally applicable for the current version of TiddlyWiki (&lt;&lt;version&gt;&gt;). Please view [[CoreTweaksArchive]] for tweaks and modifications that may be used with earlier versions of TiddlyWiki.''
----
***/
// // {{block{
/***
!!!757 add removeOptionCookie() function
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/757 - OPEN
When a TW option is reset to it's hard-coded default value, the corresponding browser cookie is usually just set to that default value, which results in an accumulation of unnecessary cookies.  Unfortunately, there is a browser-imposed limit on the number of cookies that are stored for any given domain and, when that limit is reached, the browser starts removing cookies on it's own, thereby unexpectedly discarding some TW settings.  In order to allow core and/or plugin code to 'clean up after themselves' and remove unneeded cookies, this tweak provides a new 'core' function, removeOptionCookie() that is the inverse of the existing saveOptionCookie(), and results in the actual deletion of the browser cookie associated with the specified TW option.
***/
//{{{
if (window.removeOptionCookie===undefined) {
window.removeOptionCookie=function(cookie) {
	var ex=new Date();
	ex.setTime(ex.getTime()-1000);  // immediately expire cookie
	document.cookie = cookie+&quot;=novalue; path=/; expires=&quot;+ex.toGMTString();
}
}
//}}}
// // }}}}}}// // {{block{
/***
!!!749 ieCreatePath fixup for handling / in UNC paths
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/749 - OPEN
***/
//{{{
// tweak ieCreatePath to add fallback check for / (in addition to current check for \)
var fn=window.ieCreatePath;
fn=fn.toString().replace(/function ieCreatePath\(path\)/,'window.ieCreatePath=function(path)');
fn=fn.toString().replace(/var pos = path.lastIndexOf\(&quot;\\\\&quot;\);/,
	'var pos=path.lastIndexOf(&quot;\\\\&quot;); if(pos==-1) pos=path.lastIndexOf(&quot;/&quot;);');
eval(fn);
//}}}
// // }}}}}}// // {{block{
/***
!!!742 #animate:... paramifier
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/742 - OPEN
Occasionally, when using some low-end browser/hardware combinations, TiddlyWiki documents can &quot;bog down&quot; and become almost unusable due to poor responsiveness.  Fortunately, disabling animation effects (via &lt;&lt;option chkAnimate&gt;&gt;) often makes a big difference in performance.  Unfortunately, clearing this checkbox can be nearly impossible if the document in question is already bogged down due to animations.  The &quot;#animate:true/false&quot; paramifier avoids this problem by allowing the user to set/clear the 'chkAnimate' flag on-the-fly while the URL is being initially loaded into the browser.
***/
//{{{
config.paramifiers.animate= {
	onstart: function(v) {
		config.options.chkAnimate=eval(v);
	}
};
//}}}
// // }}}}}}// // {{block{
/***
!!!741 allow &quot;&quot;&quot;&lt;hr&gt;&quot;&quot;&quot; directly in wiki-formatted content
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/741 - OPEN
This tweak extends the 'horizontal rule' formatter to recognize {{{&lt;hr&gt;}}} (or {{{&lt;hr /&gt;}}}) directly in tiddler content without being enclosed within an HTML block (i.e., {{{&lt;html&gt;&lt;hr&gt;&lt;/html&gt;}}}).  This allows HR elements to be used within table cell content, bullet items and other ''line-mode'' syntax, where the required use of newlines surrounding the &quot;&quot;&quot;----&quot;&quot;&quot; syntax would interfere with the enclosing line-mode formatting.
***/
//{{{
config.formatters[config.formatters.findByField(&quot;name&quot;,&quot;rule&quot;)].match+=&quot;|&lt;hr ?/?&gt;\\n?&quot;;
//}}}
// // }}}}}}// // {{block{
/***
!!!683 FireFox3 Import bug: &quot;browse&quot; button replacement
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/683 - OPEN
see also http://trac.tiddlywiki.org/ticket/604 - cross-platform askForFilename()
The web standard &quot;type=file&quot; input control that has been used as a local path/file picker for TiddlyWiki no longer works as expected in FireFox3, which has, for &quot;security&quot; reasons, limited javascript access to this control so that *no* local filesystem path information can be revealed, even when it is intentional and necessary, as it is with TiddlyWiki.  This tweak provides alternative HTML source that patches the backstage import panel.  It replaces the &quot;type=file&quot; input control with a text+button combination of controls that invokes a system-native secure 'file-chooser' dialog box to provide TiddlyWiki with access to a complete path+filename so that TW functions properly locate user-selected local files.
***/
//{{{
if (window.Components) {
	var fixhtml='&lt;input name=&quot;txtBrowse&quot; style=&quot;width:30em&quot;&gt;&lt;input type=&quot;button&quot; value=&quot;...&quot;'
		+' onClick=&quot;window.browseForFilename(this.previousSibling,true)&quot;&gt;';
	var cmi=config.macros.importTiddlers;
	cmi.step1Html=cmi.step1Html.replace(/&lt;input type='file' size=50 name='txtBrowse'&gt;/,fixhtml);
}

merge(config.messages,{selectFile:&quot;Please enter or select a file&quot;}); // ready for I18N translation

window.browseForFilename=function(target,mustExist) { // note: both params are optional
	var msg=config.messages.selectFile;
	if (target &amp;&amp; target.title) msg=target.title; // use target field tooltip (if any) as dialog prompt text
	// get local path for current document
	var path=getLocalPath(document.location.href);
	var p=path.lastIndexOf(&quot;/&quot;); if (p==-1) p=path.lastIndexOf(&quot;\\&quot;); // Unix or Windows
	if (p!=-1) path=path.substr(0,p+1); // remove filename, leave trailing slash
	var file=&quot;&quot;
	var result=window.askForFilename(msg,path,file,mustExist); // requires #604
	if (target &amp;&amp; result.length) // set target field and trigger handling
		{ target.value=result; target.onchange(); }
	return result; 
}
//}}}
// // }}}}}}// // {{block{
/***
!!!604 cross-platform askForFilename()
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/604 - OPEN
invokes a system-native secure 'file-chooser' dialog box to provide TiddlyWiki with access to a complete path+filename so that TW functions properly locate user-selected local files.
***/
//{{{
window.askForFilename=function(msg,path,file,mustExist) {
	var r = window.mozAskForFilename(msg,path,file,mustExist);
	if(r===null || r===false)
		r = window.ieAskForFilename(msg,path,file,mustExist);
	if(r===null || r===false)
		r = window.javaAskForFilename(msg,path,file,mustExist);
	if(r===null || r===false)
		r = prompt(msg,path+file);
	return r||&quot;&quot;;
}

window.mozAskForFilename=function(msg,path,file,mustExist) {
	if(!window.Components) return false;
	try {
		netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
		var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
		var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
		picker.init(window, msg, mustExist?nsIFilePicker.modeOpen:nsIFilePicker.modeSave);
		var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
		thispath.initWithPath(path);
		picker.displayDirectory=thispath;
		picker.defaultExtension='html';
		picker.defaultString=file;
		picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
		if (picker.show()!=nsIFilePicker.returnCancel)
			var result=picker.file.persistentDescriptor;
	}
	catch(ex) { displayMessage(ex.toString()); }
	return result;
}

window.ieAskForFilename=function(msg,path,file,mustExist) {
	if(!config.browser.isIE) return false;
	try {
		var s = new ActiveXObject('UserAccounts.CommonDialog');
		s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
		s.FilterIndex=3; // default to HTML files;
		s.InitialDir=path;
		s.FileName=file;
		return s.showOpen()?s.FileName:&quot;&quot;;
	}
	catch(ex) { displayMessage(ex.toString()); }
	return result;
}

window.javaAskForFilename=function(msg,path,file,mustExist) {
	if(!document.applets[&quot;TiddlySaver&quot;]) return false;
	// TBD: implement java-based askFile(...) function
	try { return document.applets[&quot;TiddlySaver&quot;].askFile(msg,path,file,mustExist); } 
	catch(ex) { displayMessage(ex.toString()); }
}
//}}}
// // }}}}}}// // {{block{
/***
!!!676 #story:... paramifier
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/676 - OPEN
scan the specified 'story' tiddler content for embedded links, rather than simply parsing the content as a space-separated bracketed list.  This allows links from ''any'' tiddler to be used as a story, regardless of other wiki-syntax contained in that tiddler.  If specified tiddler is a shadow, fallback to using parseParams() to extract the list of links.
***/
//{{{
config.paramifiers.story = {
	onstart: function(v) {
		var t=store.getTiddler(v); if (t) t.changed();
		var list=t?t.links:store.getTiddlerText(v,&quot;&quot;).parseParams(&quot;open&quot;,null,false);
		story.displayTiddlers(null,list);
	}
};
//}}}
// // }}}}}}// // {{block{
/***
!!!664 Loose links (case-folded/space-folded wiki words)
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/664 - OPEN
This tweak matches non-WikiWord variations of mixed-case and/or added/omitted spaces within double-bracketed text with titles of //existing// tiddlers, using a &quot;loose&quot; (case-folded/space-folded) comparison.  This allows text that occurs in normal prose to be more easily linked to tiddler titles by using double-brackets without the full &quot;pretty link&quot; syntax.  For example:
{{{
[[CoreTweaks]], [[coreTweaks]], [[core tweaks]],
[[CORE TWEAKS]], [[CoRe TwEaKs]], [[coreTWEAKS]]
}}}
&gt;[[CoreTweaks]], [[coreTweaks]], [[core tweaks]],
&gt;[[CORE TWEAKS]], [[CoRe TwEaKs]], [[coreTWEAKS]]
Configuration:
&gt;&lt;&lt;option chkLooseLinks&gt;&gt; Allow case-folded and/or space-folded text to link to existing tiddler titles
&gt;&quot;&quot;&quot;&lt;&lt;option chkLooseLinks&gt;&gt;&quot;&quot;&quot;
***/
//{{{
if (!config.options.chkLooseLinks)
	config.options.chkLooseLinks=false; // default to standard behavior
window.caseFold_createTiddlyLink = window.createTiddlyLink;
window.createTiddlyLink = function(place,title,includeText,className) {
	var btn=window.caseFold_createTiddlyLink.apply(this,arguments); // create core link
	if (!config.options.chkLooseLinks) return btn;
	if (store.getTiddlerText(title)) return btn; // matching tiddler (or shadow) exists
	var target=title.toLowerCase().replace(/\s/g,&quot;&quot;);
	var tids=store.getTiddlers(&quot;title&quot;);
	for (var t=0; t&lt;tids.length; t++) {
		if (tids[t].title.toLowerCase().replace(/\s/g,&quot;&quot;)==target) {
			var i=getTiddlyLinkInfo(tids[t].title,className);
			btn.setAttribute(&quot;tiddlyLink&quot;,tids[t].title);
			btn.title=i.subTitle;
			btn.className=i.classes;
			break;
		}
	}
	return btn;
}
//}}}
// // }}}}}}// // {{block{
/***
!!!657 wrap tabs onto multiple lines
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/657 - OPEN
This tweak inserts an extra space element following each tab, allowing them to wrap onto multiple lines if needed.
***/
//{{{
config.macros.tabs.handler = function(place,macroName,params)
{
	var cookie = params[0];
	var numTabs = (params.length-1)/3;
	var wrapper = createTiddlyElement(null,&quot;div&quot;,null,&quot;tabsetWrapper &quot; + cookie);
	var tabset = createTiddlyElement(wrapper,&quot;div&quot;,null,&quot;tabset&quot;);
	tabset.setAttribute(&quot;cookie&quot;,cookie);
	var validTab = false;
	for(var t=0; t&lt;numTabs; t++) {
		var label = params[t*3+1];
		var prompt = params[t*3+2];
		var content = params[t*3+3];
		var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,&quot;tab tabUnselected&quot;);
		createTiddlyElement(tab,&quot;span&quot;,null,null,&quot; &quot;,{style:&quot;font-size:0pt;line-height:0px&quot;}); // ELS
		tab.setAttribute(&quot;tab&quot;,label);
		tab.setAttribute(&quot;content&quot;,content);
		tab.title = prompt;
		if(config.options[cookie] == label)
			validTab = true;
	}
	if(!validTab)
		config.options[cookie] = params[1];
	place.appendChild(wrapper);
	this.switchTab(tabset,config.options[cookie]);
};
//}}}
// // }}}}}}// // {{block{
/***
!!!637 TiddlyLink tooltip - custom formatting
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/637 - OPEN
This tweak modifies the tooltip format that appears when you mouseover a link to a tiddler.  It adds an option to control the date format, as well as displaying the size of the tiddler (in bytes)

Tiddler link tooltip format:
{{stretch{&lt;&lt;option txtTiddlerLinkTootip&gt;&gt;}}}
^^where: %0=title, %1=username, %2=modification date, %3=size in bytes, %4=description slice^^
Tiddler link tooltip date format:
{{stretch{&lt;&lt;option txtTiddlerLinkTooltipDate&gt;&gt;}}}
***/
//{{{
config.messages.tiddlerLinkTooltip=&quot;%0 - %1, %2 (%3 bytes) - %4&quot;;
config.messages.tiddlerLinkTooltipDate=&quot;DDD, MMM DDth YYYY 0hh12:0mm AM&quot;;

config.options.txtTiddlerLinkTootip=
	config.options.txtTiddlerLinkTootip||config.messages.tiddlerLinkTooltip;
config.options.txtTiddlerLinkTooltipDate=
	config.options.txtTiddlerLinkTooltipDate||config.messages.tiddlerLinkTooltipDate;

Tiddler.prototype.getSubtitle = function() {
	var modifier = this.modifier;
	if(!modifier) modifier = config.messages.subtitleUnknown;
	var modified = this.modified;
	if(modified) modified = modified.formatString(config.options.txtTiddlerLinkTooltipDate);
	else modified = config.messages.subtitleUnknown;
	var descr=store.getTiddlerSlice(this.title,&quot;Description&quot;)||&quot;&quot;;
	return config.options.txtTiddlerLinkTootip.format([this.title,modifier,modified,this.text.length,descr]);
};
//}}}
// // }}}}}}// // {{block{
/***
!!!628 hide &quot;no such macro&quot; errors
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/628 - OPEN
When invoking a macro that is not defined, this tweak prevents the display of the &quot;error in macro... no such macro&quot; message.  This is useful when rendering tiddler content or templates that reference macros that are defined by //optional// plugins that have not been installed in the current document.

&lt;&lt;option chkHideMissingMacros&gt;&gt; hide &quot;no such macro&quot; error messages
***/
//{{{
if (config.options.chkHideMissingMacros===undefined)
	config.options.chkHideMissingMacros=false;

window.coreTweaks_missingMacro_invokeMacro = window.invokeMacro;
window.invokeMacro = function(place,macro,params,wikifier,tiddler) {
	if (!config.macros[macro] || !config.macros[macro].handler)
		if (config.options.chkHideMissingMacros) return;
	window.coreTweaks_missingMacro_invokeMacro.apply(this,arguments);
}
//}}}
// // }}}}}}// // {{block{
/***
!!!609/610 toolbars - separators and transclusion
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/609 - OPEN (separators)
http://trac.tiddlywiki.org/ticket/610 - OPEN (wikify tiddler/slice/section content)
These tweaks extend the &quot;&quot;&quot;&lt;&lt;toolbar&gt;&gt;&quot;&quot;&quot; macro to permit use of &quot;|&quot; as separators, as well as recognizing references to tiddlernames, slices, or sections and rendering their content inline within the toolbar
''see [[ToolbarCommands]] for examples of how these features can be used''
***/
//{{{
merge(config.macros.toolbar,{
	separator: &quot;|&quot;
	});
config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	for(var t=0; t&lt;params.length; t++) {
		var c = params[t];
		switch(c) {
			case '|':  // ELS - SEPARATOR
			case '!':  // ELS - SEPARATOR (alternative for use in tiddler slices)
				createTiddlyText(place,this.separator); // ELS
				break; // ELS
			case '&gt;':
				var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
				addClass(btn,&quot;moreCommand&quot;);
				var e = createTiddlyElement(place,&quot;span&quot;,null,&quot;moreCommand&quot;);
				e.style.display = &quot;none&quot;;
				place = e;
				break;
			default:
				var theClass = &quot;&quot;;
				switch(c.substr(0,1)) {
					case &quot;+&quot;:
						theClass = &quot;defaultCommand&quot;;
						c = c.substr(1);
						break;
					case &quot;-&quot;:
						theClass = &quot;cancelCommand&quot;;
						c = c.substr(1);
						break;
				}
				if(c in config.commands)
					this.createCommand(place,c,tiddler,theClass);
				else { // ELS - WIKIFY TIDDLER/SLICE/SECTION
					if (c.substr(0,1)==&quot;~&quot;) c=c.substr(1); // ignore leading ~
					var txt=store.getTiddlerText(c);
					if (txt) {
						txt=txt.replace(/^\n*/,&quot;&quot;).replace(/\n*$/,&quot;&quot;); // trim any leading/trailing newlines
						txt=txt.replace(/^\{\{\{\n/,&quot;&quot;).replace(/\n\}\}\}$/,&quot;&quot;); // trim PRE format wrapper if any
						wikify(txt,createTiddlyElement(place,&quot;span&quot;),null,tiddler);
					}
				} // ELS - end WIKIFY CONTENT
				break;
		}
	}
};
//}}}
// // }}}}}}// // {{block{
/***
!!!608 toolbar - more/less toggle
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/608 - OPEN
This tweak extends the &quot;&quot;&quot;&lt;&lt;toolbar&gt;&gt;&quot;&quot;&quot; macro to make the &quot;&gt;&quot; (more) a //toggle// between more/less with the additional toolbar commands displayed on a separate line.
***/
//{{{
merge(config.macros.toolbar,{
	moreLabel: 'more',
	morePrompt: &quot;Show additional commands&quot;,
	lessLabel: 'less',
	lessPrompt: &quot;Hide additional commands&quot;
});
config.macros.toolbar.onClickMore = function(ev)
{
	var e = this.nextSibling;
	var showing=e.style.display==&quot;block&quot;;
	e.style.display = showing?&quot;none&quot;:&quot;block&quot;;
	this.innerHTML=showing?config.macros.toolbar.moreLabel:config.macros.toolbar.lessLabel;
	this.title=showing?config.macros.toolbar.morePrompt:config.macros.toolbar.lessPrompt;
	return false;
};
//}}}
// // }}}}}}// // {{block{
/***
!!!607 add HREF link on permaview command
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/607 - OPEN
This tweak automatically sets the HREF for the 'permaview' sidebar command link so you can use the 'right click' context menu for faster, easier bookmarking.  Note that this does ''not'' automatically set the permaview in the browser's current location URL... it just sets the HREF on the command link.  You still have to click the link to apply the permaview.
***/
//{{{
config.macros.permaview.handler = function(place)
{
	var btn=createTiddlyButton(place,this.label,this.prompt,this.onClick);
	addEvent(btn,&quot;mouseover&quot;,this.setHREF);
	addEvent(btn,&quot;focus&quot;,this.setHREF);
};
config.macros.permaview.setHREF = function(event){
	var links = [];
	story.forEachTiddler(function(title,element) {
		links.push(String.encodeTiddlyLink(title));
	});
	var newURL=document.location.href;
	var hashPos=newURL.indexOf(&quot;#&quot;);
	if (hashPos!=-1) newURL=newURL.substr(0,hashPos);
	this.href=newURL+&quot;#&quot;+encodeURIComponent(links.join(&quot; &quot;));
}
//}}}
// // }}}}}}// // {{block{
/***
!!!529 IE fixup - case-sensitive element lookup of tiddler elements
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/529 - OPEN
This tweak hijacks the standard browser function, document.getElementById(), to work-around the case-INsensitivity error in Internet Explorer (all versions up to and including IE7) //''Note: This tweak is only applied when using IE, and only for lookups of rendered tiddler elements within the containing &quot;tiddlerDisplay&quot; element.''//
***/
//{{{
if (config.browser.isIE) {
document.coreTweaks_coreGetElementById=document.getElementById;
document.getElementById=function(id) {
	var e=document.coreTweaks_coreGetElementById(id);
	if (!e || !e.parentNode || e.parentNode.id!=&quot;tiddlerDisplay&quot;) return e;
	for (var i=0; i&lt;e.parentNode.childNodes.length; i++)
		if (id==e.parentNode.childNodes[i].id) return e.parentNode.childNodes[i];
	return null;
};
}
//}}}
// // }}}}}}// // {{block{
/***
!!!471 'creator' field for new tiddlers
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/471 - OPEN
This tweak HIJACKS the core's saveTiddler() function to automatically add a &quot;creator&quot; field to a tiddler when it is FIRST created. You can use &quot;&quot;&quot;&lt;&lt;view creator&gt;&gt;&quot;&quot;&quot; (or &quot;&quot;&quot;&lt;&lt;view creator wikified&gt;&gt;&quot;&quot;&quot; if you prefer) to show this value embedded directly within the tiddler content, or {{{&lt;span macro=&quot;view creator&quot;&gt;&lt;/span&gt;}}} in the ViewTemplate and/or EditTemplate to display the creator value in each tiddler.  
***/
//{{{
// hijack saveTiddler()
TiddlyWiki.prototype.CoreTweaks_creatorSaveTiddler=TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler=function(title,newTitle,newBody,modifier,modified,tags,fields)
{
	var existing=store.tiddlerExists(title);
	var tiddler=this.CoreTweaks_creatorSaveTiddler.apply(this,arguments);
	if (!existing) store.setValue(title,&quot;creator&quot;,config.options.txtUserName);
	return tiddler;
}
//}}}
// // }}}}}}// // {{block{
/***
!!!458 add permalink-like HREFs on internal TiddlyLinks
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/458 - CLOSED: WON'T FIX
This tweak assigns a &quot;permalink&quot;-like HREF to internal Tiddler links (which normally do not have any HREF defined).  This permits the link's context menu (right-click) to include 'open link in another window/tab' command.  Based on a request from Dustin Spicuzza.
***/
//{{{
window.coreTweaks_createTiddlyLink=window.createTiddlyLink;
window.createTiddlyLink=function(place,title,includeText,theClass,isStatic,linkedFromTiddler,noToggle)
{
	// create the core button, then add the HREF (to internal links only)
	var link=window.coreTweaks_createTiddlyLink.apply(this,arguments);
	if (!isStatic)
		link.href=document.location.href.split(&quot;#&quot;)[0]+&quot;#&quot;+encodeURIComponent(String.encodeTiddlyLink(title));
	return link;
}
//}}}
// // }}}}}}// // {{block{
/***
!!!444 'tiddler' and 'place' - global variables for use in computed macro parameters
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/444 - OPEN
When invoking a macro, this tweak makes the current containing tiddler object and DOM rendering location available as global variables (window.tiddler and window.place, respectively).  These globals can then be used within &quot;computed macro parameters&quot; to retrieve tiddler-relative and/or DOM-relative values or perform tiddler-specific side-effect functionality.
***/
//{{{
window.coreTweaks_invokeMacro = window.invokeMacro;
window.invokeMacro = function(place,macro,params,wikifier,tiddler) {
	var here=story.findContainingTiddler(place);
	window.tiddler=here?store.getTiddler(here.getAttribute(&quot;tiddler&quot;)):null;
	window.place=place;
	window.coreTweaks_invokeMacro.apply(this,arguments);
}
//}}}
// // }}}}}}// // {{block{
/***
!!!401 PageTitle - alternative to combined ~SiteTitle/~SiteSubtitle in window titlebar
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/401 - CLOSED: WON'T FIX
This tweak allows definition of an optional [[PageTitle]] tiddler that, when present, provides alternative text for display in the browser window's titlebar, instead of using the combined text content from [[SiteTitle]] and [[SiteSubtitle]] (which will still be displayed as usual in the TiddlyWiki document header area)
***/
//{{{
window.coreTweaks_getPageTitle=window.getPageTitle;
window.getPageTitle=function() { 
	var txt=wikifyPlain(&quot;PageTitle&quot;); if (txt.length) return txt;
	return window.coreTweaks_getPageTitle.apply(this,arguments);
}
store.addNotification(&quot;PageTitle&quot;,refreshPageTitle); // so title stays in sync with tiddler changes
//}}}
// // }}}}}}// // {{block{
/***
!!!67 Missing links - ignore non-wiki syntax source content
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/67 - OPEN
The &quot;missing links&quot; list includes items contained within &quot;quoted&quot; text (i.e., content that will not render as wiki-syntax, and so CANNOT create any tiddler links, even if the quoted text matches valid link syntax).  This tweak removes content contained between certain delimiters before scanning tiddler source for possible links.

Delimiters include:
{{{
/%...%/
{{{...}}}
&quot;&quot;&quot;...&quot;&quot;&quot;
&lt;nowiki&gt;...&lt;/nowiki&gt;
&lt;html&gt;...&lt;/html&gt;
&lt;script&gt;...&lt;/script&gt;
}}}
***/
//{{{
Tiddler.prototype.coreTweaks_changed = Tiddler.prototype.changed;
Tiddler.prototype.changed = function()
{
	var savedtext=this.text;
	// remove 'quoted' text before scanning tiddler source
	this.text=this.text.replace(/\/%((?:.|\n)*?)%\//g,&quot;&quot;); // /%...%/
	this.text=this.text.replace(/\{{3}((?:.|\n)*?)\}{3}/g,&quot;&quot;); // {{{...}}}
	this.text=this.text.replace(/&quot;{3}((?:.|\n)*?)&quot;{3}/g,&quot;&quot;); // &quot;&quot;&quot;...&quot;&quot;&quot;
	this.text=this.text.replace(/\&lt;nowiki\&gt;((?:.|\n)*?)\&lt;\/nowiki\&gt;/g,&quot;&quot;); // &lt;nowiki&gt;...&lt;/nowiki&gt;
	this.text=this.text.replace(/\&lt;html\&gt;((?:.|\n)*?)\&lt;\/html\&gt;/g,&quot;&quot;); // &lt;html&gt;...&lt;/html&gt;
	this.text=this.text.replace(/\&lt;script((?:.|\n)*?)\&lt;\/script\&gt;/g,&quot;&quot;); // &lt;script&gt;...&lt;/script&gt;
	this.coreTweaks_changed.apply(this,arguments);
	// restore quoted text to tiddler source
	this.text=savedtext;
};
//}}}
// // }}}}}}// // {{block{
/***
!!!(no ticket) &quot;&quot;&quot;&lt;&lt;tag&gt;&gt;&quot;&quot;&quot; macro - sortby parameter
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/TBD - TBD
This tweak adds an optional 'sortby' parameter to the &quot;&quot;&quot;&lt;&lt;tag tagname label tip sortby&gt;&gt;&quot;&quot;&quot; macro, as well as the &quot;&quot;&quot;&lt;&lt;allTags excludeTag sortby&gt;&gt;&quot;&quot;&quot; macro used to generate the sidebar contents 'tags' list.  Specify the field on which the contents of each tag popup is to be sorted, with a &quot;+&quot; or &quot;-&quot; prefix to indicate ascending/descending order, respectively.

Example: &quot;&quot;&quot;&lt;&lt;tag systemConfig &quot;plugins&quot; &quot;list plugins by date, most recent first&quot; &quot;-modified&quot;&gt;&gt;&quot;&quot;&quot;
Try it: &lt;&lt;tag systemConfig &quot;plugins&quot; &quot;list plugins by date, most recent first&quot; &quot;-modified&quot;&gt;&gt;

Similarly, to change the sort order used by the popups from all tags shown in the sidebar contents, edit the [[TagTags]] shadow tiddler and enter: &quot;&quot;&quot;&lt;&lt;allTags excludeLists -modified&gt;&gt;&quot;&quot;&quot;
***/
//{{{
// hijack tag handler() to add 'sortby' attribute to tag button
config.macros.tag.CoreTweaksSortTags_handler=config.macros.tag.handler;
config.macros.tag.handler = function(place,macroName,params)
{
	this.CoreTweaksSortTags_handler.apply(this,arguments);
	var btn=place.lastChild;
	if (params[3]) btn.setAttribute(&quot;sortby&quot;,params[3]);
}

// tweak &lt;&lt;allTags&gt;&gt; macro to add 'sortby' attribute to each tag button
var fn=config.macros.allTags.handler;
var lines=fn.toString().split(&quot;\n&quot;);
lines.splice(lines.length-2,0,['if(params[1]) btn.setAttribute(&quot;sortby&quot;,params[1]);']);
fn=lines.join(&quot;\n&quot;);
eval(&quot;config.macros.allTags.handler=&quot;+fn);

// tweak event handler for clicking on a tiddler tag to use 'sortby' attribute
var fn=onClickTag;
fn=fn.toString().replace(
	/store.getTaggedTiddlers\(tag\);/g,
	'store.getTaggedTiddlers(tag);'
	+'var sortby=this.getAttribute(&quot;sortby&quot;);'
	+'if(sortby&amp;&amp;sortby.length) store.sortTiddlers(tagged,sortby);'
);
eval(fn);

//}}}
// // }}}}}}// // {{block{
/***
!!!(no ticket) backslash-quoting for embedding newlines in 'line-mode' formats
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/TBD - TBD
This HIJACK tweak pre-processes source content to convert &quot;double-backslash-newline&quot; into {{{&lt;br&gt;}}} before wikify(), so that literal newlines can be embedded in line-mode wiki syntax (e.g., tables, bullets, etc.).  Based on a suggestion from Sitaram Chamarty.
***/
//{{{
window.coreWikify = wikify;
window.wikify = function(source,output,highlightRegExp,tiddler)
{
	if (source) arguments[0]=source.replace(/\\\\\n/mg,&quot;&lt;br&gt;&quot;);
	coreWikify.apply(this,arguments);
}
//}}}
// // }}}}}}
// // &lt;&lt;foldHeadings&gt;&gt;</pre>
</div>
<div title="DailyScrum" modifier="YBA" created="200809181218" changecount="1" creator="YBA">
<pre>* qu'as tu fait hier ?
* que va-tu faire aujourd'hui ?
* qu'est-ce qui t'empêche de faire ton travail ?
''15 min max''</pre>
</div>
<div title="DataTiddlerPlugin" modifier="YBA" created="200809191424" modified="200809220808" tags="systemConfig excludeSearch excludeLists" changecount="7">
<pre>/***
|''Name''|DataTiddlerPlugin - fields version|
|''Description''|Same as original|
|''Author''|[[YBA - IDEIA &lt;ybabel@ideia.fr&gt;|mailto:Y.%20BABEL&lt;ybabel@ideia.fr&gt;?subject=NewDataTiddlerPlugin]]|
|''Version''|0.1|
|''Date''|2008.09.19|
|''Status''|@@beta@@|
|''License''|[[GNU/GPL3+|http://www.gnu.org/licenses/gpl-3.0.html]]|
|''CoreVersion''|2.4|
|''Requires''||
|''Overrides''|DataTiddlerPlugin from Udo|

!Code
***/
//{{{

// Ensure that the DataTiddler Plugin is only installed once.
//
if (!version.extensions.DataTiddlerPlugin) {

version.extensions.DataTiddlerPlugin = {
    major: 1, minor: 0, revision: 6,
    date: new Date(2006, 7, 26), 
    type: 'plugin',
    source: &quot;http://tiddlywiki.abego-software.de/#DataTiddlerPlugin&quot;
};

// For backward compatibility with v1.2.x
//
if (!window.story) window.story=window; 
if (!TiddlyWiki.prototype.getTiddler) {
	TiddlyWiki.prototype.getTiddler = function(title) { 
		var t = this.tiddlers[title]; 
		return (t !== undefined &amp;&amp; t instanceof Tiddler) ? t : null; 
	};
}

//============================================================================
// DataTiddler Class
//============================================================================

// ---------------------------------------------------------------------------
// Configurations and constants 
// ---------------------------------------------------------------------------

function DataTiddler() {
}

DataTiddler = {
    // Function to stringify a JavaScript value, producing the text for the data section content.
    // (Must match the implementation of DataTiddler.parse.)
    //
    stringify : null,
    

    // Function to parse the text for the data section content, producing a JavaScript value.
    // (Must match the implementation of DataTiddler.stringify.)
    //
    parse : null
};

// Ensure access for IE
window.DataTiddler = DataTiddler;

// ---------------------------------------------------------------------------
// Data Accessor and Mutator
// ---------------------------------------------------------------------------


// Returns the value of the given data field of the tiddler.
// When no such field is defined or its value is undefined
// the defaultValue is returned.
// 
// @param tiddler either a tiddler name or a tiddler
//
DataTiddler.getData = function(tiddler, field, defaultValue) {
    var t = (typeof tiddler == &quot;string&quot;) ? store.getTiddler(tiddler) : tiddler;
    if (!(t instanceof Tiddler)) {
        throw &quot;Tiddler expected. Got &quot;+tiddler;
    }

    return DataTiddler.getTiddlerDataValue(t, field, defaultValue);
};


// Sets the value of the given data field of the tiddler to
// the value. When the value is equal to the defaultValue
// no value is set (and the field is removed)
//
// Changing data of a tiddler will not trigger notifications.
// 
// @param tiddler either a tiddler name or a tiddler
//
DataTiddler.setData = function(tiddler, field, value, defaultValue) {
    var t = (typeof tiddler == &quot;string&quot;) ? store.getTiddler(tiddler) : tiddler;
    if (!(t instanceof Tiddler)) {
        throw &quot;Tiddler expected. Got &quot;+tiddler+ &quot;(&quot;+t+&quot;)&quot;;
    }

    DataTiddler.setTiddlerDataValue(t, field, value, defaultValue);
};


// Returns the data object of the tiddler, with a property for every field.
//
// The properties of the returned data object may only be read and
// not be modified. To modify the data use DataTiddler.setData(...) 
// or the corresponding Tiddler method.
//
// If no data section is defined a new (empty) object is returned.
//
// @param tiddler either a tiddler name or a Tiddler
//
DataTiddler.getDataObject = function(tiddler) {
    var t = (typeof tiddler == &quot;string&quot;) ? store.getTiddler(tiddler) : tiddler;
    if (!(t instanceof Tiddler)) {
        throw &quot;Tiddler expected. Got &quot;+tiddler;
    }

    return DataTiddler.getTiddlerDataObject(t);
};

// ---------------------------------------------------------------------------
// Internal helper methods (must not be used by code from outside this plugin)
// ---------------------------------------------------------------------------

// Internal.
//
// @param t a Tiddler
//
DataTiddler.getTiddlerDataObject = function(t) {
    return t.fields;
};


// Internal.
//
// @param tiddler a Tiddler
//
DataTiddler.getTiddlerDataValue = function(tiddler, field, defaultValue) {
    var value = DataTiddler.getTiddlerDataObject(tiddler)[field];
    return (value === undefined) ? defaultValue : value;
};


// Internal.
//
// @param tiddler a Tiddler
//
DataTiddler.setTiddlerDataValue = function(tiddler, field, value, defaultValue) {
    var data = DataTiddler.getTiddlerDataObject(tiddler);
    var oldValue = data[field];
	
    if (value == defaultValue) {
        if (oldValue !== undefined) {
            delete data[field];
            DataTiddler.save(tiddler);
        }
        return;
    }
    data[field] = value;
    DataTiddler.save(tiddler);
};

DataTiddler.save = function(tiddler) {
        // Mark the store as dirty.
        store.dirty = true;
 
        // AutoSave if option is selected
        if(config.options.chkAutoSave) {
           saveChanges();
        }
};
//============================================================================
// Tiddler Class Extension
//============================================================================

// &quot;Hijack&quot; the changed method ---------------------------------------------------


// Define accessor methods -------------------------------------------------------

// Returns the value of the given data field of the tiddler. When no such field 
// is defined or its value is undefined the defaultValue is returned.
//
// When field is undefined (or null) the data object is returned. (See 
// DataTiddler.getDataObject.)
//
// @param field [may be null, undefined]
// @param defaultValue [may be null, undefined]
// @return [may be null, undefined]
//
Tiddler.prototype.data = function(field, defaultValue) {
    return (field) 
         ? DataTiddler.getTiddlerDataValue(this, field, defaultValue)
         : DataTiddler.getTiddlerDataObject(this);
};

// Sets the value of the given data field of the tiddler to the value. When the 
// value is equal to the defaultValue no value is set (and the field is removed).
//
// @param value [may be null, undefined]
// @param defaultValue [may be null, undefined]
//
Tiddler.prototype.setData = function(field, value, defaultValue) {
    DataTiddler.setTiddlerDataValue(this, field, value, defaultValue);
};


//============================================================================
// showData Macro
//============================================================================

config.macros.showData = {
     // Standard Properties
     label: &quot;showData&quot;,
     prompt: &quot;Display the values stored in the data section of the tiddler&quot;
};

config.macros.showData.handler = function(place,macroName,params) {
    // --- Parsing ------------------------------------------

    var i = 0; // index running over the params
    // Parse the optional &quot;JSON&quot;
    var showInJSONFormat = false;
    if ((i &lt; params.length) &amp;&amp; params[i] == &quot;JSON&quot;) {
        i++;
        showInJSONFormat = true;
    }
    
    var tiddlerName = story.findContainingTiddler(place).id.substr(7);
    if (i &lt; params.length) {
        tiddlerName = params[i];
        i++;
    }

    // --- Processing ------------------------------------------
    try {
        if (showInJSONFormat) {
            this.renderDataInJSONFormat(place, tiddlerName);
        } else {
            this.renderDataAsTable(place, tiddlerName);
        }
    } catch (e) {
        this.createErrorElement(place, e);
    }
};

config.macros.showData.renderDataInJSONFormat = function(place,tiddlerName) {
    var text = DataTiddler.getDataText(tiddlerName);
    if (text) {
        createTiddlyElement(place,&quot;pre&quot;,null,null,text);
    }
};

config.macros.showData.renderDataAsTable = function(place,tiddlerName) {
    var text = &quot;|!Name|!Value|\n&quot;;
    var data = DataTiddler.getDataObject(tiddlerName);
    if (data) {
        for (var i in data) {
            var value = data[i];
            text += &quot;|&quot;+i+&quot;|&quot;+DataTiddler.stringify(value)+&quot;|\n&quot;;
        }
    }
    
    wikify(text, place);
};


// Internal.
//
// Creates an element that holds an error message
// 
config.macros.showData.createErrorElement = function(place, exception) {
    var message = (exception.description) ? exception.description : exception.toString();
    return createTiddlyElement(place,&quot;span&quot;,null,&quot;showDataError&quot;,&quot;&lt;&lt;showData ...&gt;&gt;: &quot;+message);
};

// ---------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// ---------------------------------------------------------------------------
//
setStylesheet(
    &quot;.showDataError{color: #ffffff;background-color: #880000;}&quot;,
    &quot;showData&quot;);


} // of &quot;install only once&quot;

//}}}

</pre>
</div>
<div title="DefaultTiddlers" modifier="YBA" created="200809111401" modified="200903241646" changecount="6">
<pre>[[CahierChargeInTech]]
[[RoadmapPreliminaires]]
[[RapportGlobalPreliminaires]]</pre>
</div>
<div title="DeleteAllTaggedPlugin" modifier="YBA" created="200809191249" modified="200809220808" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|DeleteAllTaggedPlugin|
|Source|http://ido-xp.tiddlyspot.com/#DeleteAllTaggedPlugin|
|Version|1.0|

An adaptation of DeleteDoneTasks (Simon Baird) by Ido Magal
To use this insert {{{&lt;&lt;deleteAllTagged&gt;&gt;}}} into the desired tiddler.

/***
Example usage:
{{{&lt;&lt;deleteAllTagged&gt;&gt;}}}
&lt;&lt;deleteAllTagged&gt;&gt;
***/
//{{{

config.macros.deleteAllTagged = {
	handler: function ( place,macroName,params,wikifier,paramString,tiddler ) {
		var buttonTitle = &quot;Delete Tagged w/ '&quot;+tiddler.title+&quot;'&quot;;
		createTiddlyButton( place, buttonTitle, &quot;Delete every tiddler tagged with '&quot;+tiddler.title+&quot;'&quot;, this.deleteAllTagged( tiddler.title ));
	},

	deleteAllTagged: function(tag) {
		return function() {
			var collected = [];
			store.forEachTiddler( function ( title,tiddler ) {
				if ( tiddler.tags.contains( tag ))
				{
					collected.push( title );
				}
			});
			if ( collected.length == 0 )
			{
				alert( &quot;No tiddlers found tagged with '&quot;+tag+&quot;'.&quot; );
			}
			else
			{
				if ( confirm( &quot;These tiddlers are tagged with '&quot;+tag+&quot;'\n'&quot;
						+ collected.join( &quot;', '&quot; ) + &quot;'\n\n\n&quot;
						+ &quot;Are you sure you want to delete these?&quot; ))
				{
					for ( var i=0;i&lt;collected.length;i++ )
					{
						store.deleteTiddler( collected[i] );
						displayMessage( &quot;Deleted '&quot;+collected[i]+&quot;'&quot; );
					}
				}
			}
		}
	}
};

//}}}</pre>
</div>
<div title="DeprecatedFunctionsPlugin" modifier="YBA" created="200809161315" modified="200809220801" tags="systemConfig excludeSearch excludeLists" changecount="4">
<pre>/***
|''Name''|DeprecatedFunctionsPlugin|
|''Description''|Provides support for functions removed from the TiddlyWiki core|
|''Version''|1.0.0|
|''Status''|stable|
|''Source''|http://www.tiddlywiki.com/coreplugins.html#DeprecatedFunctionsPlugin|
|''~CodeRepository:''|http://svn.tiddlywiki.org/Trunk/association/plugins/DeprecatedFunctionsPlugin/DeprecatedFunctionsPlugin.js |
|''License''|[[BSD open source license]]|
|''~CoreVersion''|2.3.0|
|''Feedback''|[[TiddlyWiki community|http://groups.google.com/group/TiddlyWiki]] |
|''Keywords''|legacySupport|
!Code
***/
//{{{
if(!version.extensions.DeprecatedFunctionsPlugin) {
version.extensions.DeprecatedFunctionsPlugin = {installed:true};

//--
//-- Deprecated code
//--

// @Deprecated: Use createElementAndWikify and this.termRegExp instead
config.formatterHelpers.charFormatHelper = function(w)
{
	w.subWikify(createTiddlyElement(w.output,this.element),this.terminator);
};

// @Deprecated: Use enclosedTextHelper and this.lookaheadRegExp instead
config.formatterHelpers.monospacedByLineHelper = function(w)
{
	var lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
	lookaheadRegExp.lastIndex = w.matchStart;
	var lookaheadMatch = lookaheadRegExp.exec(w.source);
	if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart) {
		var text = lookaheadMatch[1];
		if(config.browser.isIE)
			text = text.replace(/\n/g,&quot;\r&quot;);
		createTiddlyElement(w.output,&quot;pre&quot;,null,null,text);
		w.nextMatch = lookaheadRegExp.lastIndex;
	}
};

// @Deprecated: Use &lt;br&gt; or &lt;br /&gt; instead of &lt;&lt;br&gt;&gt;
config.macros.br = {};
config.macros.br.handler = function(place)
{
	createTiddlyElement(place,&quot;br&quot;);
};

// Find an entry in an array. Returns the array index or null
// @Deprecated: Use indexOf instead
Array.prototype.find = function(item)
{
	var i = this.indexOf(item);
	return i == -1 ? null : i;
};

// Load a tiddler from an HTML DIV. The caller should make sure to later call Tiddler.changed()
// @Deprecated: Use store.getLoader().internalizeTiddler instead
Tiddler.prototype.loadFromDiv = function(divRef,title)
{
	return store.getLoader().internalizeTiddler(store,this,title,divRef);
};

// Format the text for storage in an HTML DIV
// @Deprecated Use store.getSaver().externalizeTiddler instead.
Tiddler.prototype.saveToDiv = function()
{
	return store.getSaver().externalizeTiddler(store,this);
};

// @Deprecated: Use store.allTiddlersAsHtml() instead
function allTiddlersAsHtml()
{
	return store.allTiddlersAsHtml();
}

// @Deprecated: Use refreshPageTemplate instead
function applyPageTemplate(title)
{
	refreshPageTemplate(title);
}

// @Deprecated: Use story.displayTiddlers instead
function displayTiddlers(srcElement,titles,template,unused1,unused2,animate,unused3)
{
	story.displayTiddlers(srcElement,titles,template,animate);
}

// @Deprecated: Use story.displayTiddler instead
function displayTiddler(srcElement,title,template,unused1,unused2,animate,unused3)
{
	story.displayTiddler(srcElement,title,template,animate);
}

// @Deprecated: Use functions on right hand side directly instead
var createTiddlerPopup = Popup.create;
var scrollToTiddlerPopup = Popup.show;
var hideTiddlerPopup = Popup.remove;

// @Deprecated: Use right hand side directly instead
var regexpBackSlashEn = new RegExp(&quot;\\\\n&quot;,&quot;mg&quot;);
var regexpBackSlash = new RegExp(&quot;\\\\&quot;,&quot;mg&quot;);
var regexpBackSlashEss = new RegExp(&quot;\\\\s&quot;,&quot;mg&quot;);
var regexpNewLine = new RegExp(&quot;\n&quot;,&quot;mg&quot;);
var regexpCarriageReturn = new RegExp(&quot;\r&quot;,&quot;mg&quot;);

}
//}}}</pre>
</div>
<div title="Dix étapes" modifier="YBA" created="200808081100" modified="200808081101" changecount="2">
<pre>http://www.agile-software-development.com/2007/09/how-to-implement-scrum-in-10-easy-steps.html

# Créer le backlog de produit ordonné (liste des choses que les utilisateurs veulent faire) par le ProductOwner
# Estimer le backlog en points collectivement
# Planifier le sprint : durée, objectif, écrire les User Stories
# Planifier le sprint : découper en taches et estimer la durée (moins de 2 jours)
# Mettre en place l'espace collaboratif (tableau de bord)
# Lancer le sprint (pas d'interférence, qualité, TDD, XP)
# Réunion quotidienne 15 min : fait, reste à faire, blocage ?
# L'équipe met a jour le Burdown Chart
# Finir le sprint : produit livrable
# Revue, Rétrospective, Répétition</pre>
</div>
<div title="Done" modifier="YBA" created="200809151416" tags="status" changecount="1">
<pre></pre>
</div>
<div title="Démarrer" modifier="YBA" created="200809111401" modified="200810151212" changecount="59">
<pre>Lire l'[[Introduction]], définissez vos [[Paramètres]] généraux. En cas de problème consultez l'[[Aide]].
* Rédigez votre [[Backlog de produit]]
* Saisissez des idées en vrac [[Brouillon]]    
* Voir le [[Récapitulatif]]
* Sprintez (le [[Sprint0]] sert lancer le projet) &lt;&lt;newTiddler label:&quot;new Sprint&quot; tag:sprint title:&quot;Nouveau sprint&quot; text:{{store.getTiddlerText(&quot;EmptySprint&quot;)}} &gt;&gt;
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;sprint&quot;)'
write '&quot;** [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
* Taches a faire : (ou bien voir le TaskBoard - ou le [[Burdown]])
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; !tiddler.tags.contains(&quot;Done&quot;)'
write '&quot;** [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
* Vérifiez que toutes les tâches ont au moins un tag d'état :
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; !tiddler.tags.contains(&quot;InProgress&quot;) &amp;&amp; !tiddler.tags.contains(&quot;ToDo&quot;) &amp;&amp; !tiddler.tags.contains(&quot;ToVerify&quot;) &amp;&amp; !tiddler.tags.contains(&quot;Done&quot;)'
write '&quot;** [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
* Chaque jours :
** DailyScrum
** Remplir le reste à faire sur les tâches en cours [[Burdown]]</pre>
</div>
<div title="EmptyBacklog" modifier="YBA" created="200809221442" changecount="1">
<pre>Ici la liste de vos user stories
----
Ici les commentaires</pre>
</div>
<div title="EmptySprint" modifier="YBA" created="200809221407" modified="200809221441" changecount="3">
<pre>Liste triée des user stories
----
Rajoutez vos commentaires ici</pre>
</div>
<div title="EmptyTask" modifier="YBA" created="200809221442" changecount="1">
<pre>Description de la tache</pre>
</div>
<div title="EmptyUserStory" modifier="YBA" created="200809221441" changecount="1">
<pre>En tant que ... je souhaite ... afin de ...</pre>
</div>
<div title="EtapesLoadTestDrupal" modifier="YBA" created="200810151221" modified="200811051442" tags="doc" changecount="5" creator="YBA">
<pre>http://drupal.org/node/155850 a la fin, la meilleure doc sur l'architecture Drupal high load !

Determiner l'unité de mesure d'un site web : pages par minutes ?

! tests de perfs selon config drupal
* reproduire http://buytaert.net/drupal-5-performance / http://buytaert.net/drupal-webserver-configurations-compared
* monter un site Drupal vierge : Drupal 5.7 + Drupal 6.2 + site réseau social + H4Men
* installer devel, charger le site de données pour les sites vierges
* http://drupal.org/project/loadtest à l'évaluer
* http://drupal.org/project/dran à évaluer
* évaluer avec les différent caches : drupal interne, block cache, APC, memcached
* ''http://drupal.org/project/memcache''
* ''http://drupal.org/project/apc''
* http://drupal.org/project/advcache
* ''http://drupal.org/project/blockcache''
* ''http://drupal.org/project/boost''
* http://drupal.org/project/fastpath_fscache
* http://drupal.org/project/filecache
* http://drupal.org/project/resultcache
* http://drupal.org/project/smartcache
* http://drupal.org/project/sf_cache
! tests de perfs selon config serveur
* reproduire http://buytaert.net/drupal-webserver-configurations-compared
! architecture cluster
* reproduire : http://www.johnandcailin.com/blog/john/scaling-drupal-open-source-infrastructure-high-traffic-drupal-sites
* http://books.tag1consulting.com/book/export/html/1</pre>
</div>
<div title="ExportTiddlerPlugin" modifier="YBA" created="200809221317" modified="200809221317" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|ExportTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#ExportTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#ExportTiddlersPluginInfo|
|Version|2.8.1|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Description|select and extract tiddlers from your ~TiddlyWiki documents and save them to a separate file|
ExportTiddlersPlugin lets you select and extract tiddlers from your ~TiddlyWiki documents using interactive control panel lets you specify a destination, and then select which tiddlers to export. Tiddler data can be output as complete, stand-alone TiddlyWiki documents, or just the selected tiddlers (&quot;~PureStore&quot; format -- smaller files!) that can be imported directly into another ~TiddlyWiki, or as an ~RSS-compatible XML file that can be published for RSS syndication.
!!!!!Documentation
&gt;see [[ExportTiddlersPluginInfo]]
!!!!!Inline control panel (live):
&gt;&lt;&lt;exportTiddlers inline&gt;&gt;
!!!!!Revisions
&lt;&lt;&lt;
2008.09.19 [2.8.1] in formatItem(), removed unnecessary convertUnicodeToUTF8() (was causing double-conversion!)
2008.09.11 [2.8.0] extensive code cleanup: moved all global functions inside macro object. Re-wrote file generator and I/O to support TiddlyWiki, PlainText, PureStore, and NewsFeed file formats.  Replaced inline 'match tags' code with use of getMatchingTiddlers() from [[MatchTagsPlugin]] (if installed), with fallback to core getTaggedTiddlers() otherwise.
|please see [[ExportTiddlersPluginInfo]] for additional revision details|
2005.10.09 [0.0.0] development started
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
// version
version.extensions.ExportTiddlersPlugin= {major: 2, minor: 8, revision: 1, date: new Date(2008,9,19)};

// default shadow definition
config.shadowTiddlers.ExportTiddlers=&quot;&lt;&lt;exportTiddlers inline&gt;&gt;&quot;;

// add 'export' backstage task (following built-in import task)
if (config.tasks) { // TW2.2 or above
	config.tasks.exportTask = {
		text:&quot;export&quot;,
		tooltip:&quot;Export selected tiddlers to another file&quot;,
		content:&quot;&lt;&lt;exportTiddlers inline&gt;&gt;&quot;
	}
	config.backstageTasks.splice(config.backstageTasks.indexOf(&quot;importTask&quot;)+1,0,&quot;exportTask&quot;);
}

// $(...) function: 'shorthand' convenience syntax for document.getElementById()
if (typeof($)==&quot;undefined&quot;) { // avoid redefinition
function $() {
	var elements=new Array();
	for (var i=0; i&lt;arguments.length; i++) {
		var element=arguments[i];
		if (typeof element=='string') element=document.getElementById(element);
		if (arguments.length==1) return element;
		elements.push(element);
	}
	return elements;
}
}

config.macros.exportTiddlers = {
	label: &quot;export tiddlers&quot;,
	prompt: &quot;Copy selected tiddlers to an export document&quot;,
	okmsg: &quot;%0 tiddlers written to %1&quot;,
	failmsg: &quot;An error occurred while creating %1&quot;,
	mergeprompt: &quot;%0\nalready contains tiddler definitions.\n&quot;
		+&quot;\nPress OK to add new/revised tiddlers to current file contents.&quot;
		+&quot;\nPress Cancel to completely replace file contents&quot;,
	mergestatus: &quot;Merged %0 new/revised tiddlers and %1 existing tiddlers&quot;,
	statusmsg: &quot;%0 tiddler%1 - %2 selected for export&quot;,
	newdefault: &quot;export.html&quot;,
	datetimefmt: &quot;0MM/0DD/YYYY 0hh:0mm:0ss&quot;,  // for &quot;filter date/time&quot; edit fields
	type_TW: &quot;tw&quot;, type_PS: &quot;ps&quot;, type_TX: &quot;tx&quot;, type_NF: &quot;nf&quot;, // file type tokens
	type_map: { // map filetype param alternatives/abbreviations to token values
		tiddlywiki:&quot;tw&quot;, tw:&quot;tw&quot;, wiki: &quot;tw&quot;,
		purestore: &quot;ps&quot;, ps:&quot;ps&quot;, store:&quot;ps&quot;,
		plaintext: &quot;tx&quot;, tx:&quot;tx&quot;, text: &quot;tx&quot;,
		newsfeed:  &quot;nf&quot;, nf:&quot;nf&quot;, xml:  &quot;nf&quot;, rss:&quot;nf&quot;
	},
	handler: function(place,macroName,params) {
		if (params[0]!=&quot;inline&quot;)
			{ createTiddlyButton(place,this.label,this.prompt,this.togglePanel); return; }
		var panel=this.createPanel(place);
		panel.style.position=&quot;static&quot;;
		panel.style.display=&quot;block&quot;;
	},
	createPanel: function(place) {
		var panel=$(&quot;exportPanel&quot;);
		if (panel) { panel.parentNode.removeChild(panel); }
		setStylesheet(this.css,&quot;exportTiddlers&quot;);
		panel=createTiddlyElement(place,&quot;span&quot;,&quot;exportPanel&quot;,null,null)
		panel.innerHTML=this.html;
		this.initFilter();
		this.refreshList(0);
		var fn=$(&quot;exportFilename&quot;);
		if (window.location.protocol==&quot;file:&quot; &amp;&amp; !fn.value.length) {
			// get new target path/filename
			var newPath=getLocalPath(window.location.href);
			var slashpos=newPath.lastIndexOf(&quot;/&quot;); if (slashpos==-1) slashpos=newPath.lastIndexOf(&quot;\\&quot;); 
			if (slashpos!=-1) newPath=newPath.substr(0,slashpos+1); // trim filename
			fn.value=newPath+this.newdefault;
		}
		return panel;
	},
	togglePanel: function(e) {
		if (!e) var e = window.event;
		var parent=resolveTarget(e).parentNode;
		var panel = $(&quot;exportPanel&quot;);
		if (panel==undefined || panel.parentNode!=parent)
			panel=config.macros.exportTiddlers.createPanel(parent);
		var isOpen = panel.style.display==&quot;block&quot;;
		if(config.options.chkAnimate)
			anim.startAnimating(new Slider(panel,!isOpen,e.shiftKey || e.altKey,&quot;none&quot;));
		else
			panel.style.display = isOpen ? &quot;none&quot; : &quot;block&quot; ;
		if (panel.style.display!=&quot;none&quot;) { // update list and set focus when panel is made visible
			config.macros.exportTiddlers.refreshList(0);
			var fn=$(&quot;exportFilename&quot;); fn.focus(); fn.select();
		}
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
		return(false);
	},
	css: '\
		#exportPanel {\
			display: none; position:absolute; z-index:12; width:35em; right:105%; top:6em;\
			background-color: #eee; color:#000; font-size: 8pt; line-height:110%;\
			border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;\
			padding: 0.5em; margin:0em; -moz-border-radius:1em;\
		}\
		#exportPanel a, #exportPanel td a { color:#009; display:inline; margin:0px; padding:1px; }\
		#exportPanel table { \
			width:100%; border:0px; padding:0px; margin:0px;\
			font-size:8pt; line-height:110%; background:transparent;\
		}\
		#exportPanel tr { border:0px;padding:0px;margin:0px; background:transparent; }\
		#exportPanel td { color:#000; border:0px;padding:0px;margin:0px; background:transparent; }\
		#exportPanel select { width:98%;margin:0px;font-size:8pt;line-height:110%;}\
		#exportPanel input  { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%; }\
		#exportPanel textarea  { width:98%;padding:0px;margin:0px;overflow:auto;font-size:8pt; }\
		#exportPanel .box { \
			border:1px solid black; padding:3px; margin-bottom:5px; \
			background:#f8f8f8; -moz-border-radius:5px; }\
		#exportPanel .topline { border-top:2px solid black; padding-top:3px; margin-bottom:5px; }\
		#exportPanel .rad { width:auto;border:0 }\
		#exportPanel .chk { width:auto;border:0 }\
		#exportPanel .btn { width:auto; }\
		#exportPanel .btn1 { width:98%; }\
		#exportPanel .btn2 { width:48%; }\
		#exportPanel .btn3 { width:32%; }\
		#exportPanel .btn4 { width:24%; }\
		#exportPanel .btn5 { width:19%; }\
	',
	html: '\
		&lt;!-- target path/file  --&gt;\
		&lt;div&gt;\
		export to path/filename:&lt;br&gt;\
		&lt;input type=&quot;text&quot; id=&quot;exportFilename&quot; size=40 style=&quot;width:93%&quot;&gt;&lt;input \
			type=&quot;button&quot; id=&quot;exportBrowse&quot; value=&quot;...&quot; title=&quot;select or enter a local folder/file...&quot; style=&quot;width:5%&quot; \
			onclick=&quot;var fn=config.macros.exportTiddlers.askForFilename(this); if (fn.length) this.previousSibling.value=fn; &quot;&gt;\
		&lt;/div&gt;\
		&lt;!-- output format --&gt;\
		&lt;div&gt;\
		output file format:\
		&lt;select id=&quot;exportFormat&quot; size=1&gt;\
			&lt;option value=&quot;TW&quot;&gt;TiddlyWiki HTML document (includes core code)&lt;/option&gt;\
			&lt;option value=&quot;PS&quot;&gt;TiddlyWiki &quot;PureStore&quot; HTML file (tiddler data only)&lt;/option&gt;\
			&lt;option value=&quot;TX&quot;&gt;TiddlyWiki plain text TXT file (tiddler source listing)&lt;/option&gt;\
			&lt;option value=&quot;NF&quot;&gt;RSS NewsFeed XML file&lt;/option&gt;\
		&lt;/select&gt;\
		&lt;/div&gt;\
		&lt;!-- notes --&gt;\
		&lt;div&gt;\
		notes:&lt;br&gt;\
		&lt;textarea id=&quot;exportNotes&quot; rows=3 cols=40 style=&quot;height:4em;margin-bottom:5px;&quot; onfocus=&quot;this.select()&quot;&gt;&lt;/textarea&gt; \
		&lt;/div&gt;\
		&lt;!-- list of tiddlers --&gt;\
		&lt;table&gt;&lt;tr align=&quot;left&quot;&gt;&lt;td&gt;\
			select:\
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectAll&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;select all tiddlers&quot;&gt;\
				&amp;nbsp;all&amp;nbsp;&lt;/a&gt;\
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectChanges&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;select tiddlers changed since last save&quot;&gt;\
				&amp;nbsp;changes&amp;nbsp;&lt;/a&gt; \
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectOpened&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;select tiddlers currently being displayed&quot;&gt;\
				&amp;nbsp;opened&amp;nbsp;&lt;/a&gt; \
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportSelectRelated&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;select tiddlers related to the currently selected tiddlers&quot;&gt;\
				&amp;nbsp;related&amp;nbsp;&lt;/a&gt; \
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportToggleFilter&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;show/hide selection filter&quot;&gt;\
				&amp;nbsp;filter&amp;nbsp;&lt;/a&gt;  \
		&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;\
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportListSmaller&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;reduce list size&quot;&gt;\
				&amp;nbsp;&amp;#150;&amp;nbsp;&lt;/a&gt;\
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportListLarger&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;increase list size&quot;&gt;\
				&amp;nbsp;+&amp;nbsp;&lt;/a&gt;\
		&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
		&lt;select id=&quot;exportList&quot; multiple size=&quot;10&quot; style=&quot;margin-bottom:5px;&quot;\
			onchange=&quot;config.macros.exportTiddlers.refreshList(this.selectedIndex)&quot;&gt;\
		&lt;/select&gt;&lt;br&gt;\
		&lt;/div&gt;&lt;!--box--&gt;\
		&lt;!-- selection filter --&gt;\
		&lt;div id=&quot;exportFilterPanel&quot; style=&quot;display:none&quot;&gt;\
		&lt;table&gt;&lt;tr align=&quot;left&quot;&gt;&lt;td&gt;\
			selection filter\
		&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;\
			&lt;a href=&quot;JavaScript:;&quot; id=&quot;exportHideFilter&quot;\
				onclick=&quot;config.macros.exportTiddlers.process(this)&quot; title=&quot;hide selection filter&quot;&gt;hide&lt;/a&gt;\
		&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
		&lt;div class=&quot;box&quot;&gt;\
		&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=&quot;exportFilterStart&quot; value=&quot;1&quot;\
			onclick=&quot;config.macros.exportTiddlers.showFilterFields(this)&quot;&gt; starting date/time&lt;br&gt;\
		&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;&gt;&lt;tr valign=&quot;center&quot;&gt;&lt;td width=&quot;50%&quot;&gt;\
			&lt;select size=1 id=&quot;exportFilterStartBy&quot; \
				onchange=&quot;config.macros.exportTiddlers.showFilterFields(this);&quot;&gt;\
				&lt;option value=&quot;0&quot;&gt;today&lt;/option&gt;\
				&lt;option value=&quot;1&quot;&gt;yesterday&lt;/option&gt;\
				&lt;option value=&quot;7&quot;&gt;a week ago&lt;/option&gt;\
				&lt;option value=&quot;30&quot;&gt;a month ago&lt;/option&gt;\
				&lt;option value=&quot;file&quot;&gt;file date&lt;/option&gt;\
				&lt;option value=&quot;other&quot;&gt;other (mm/dd/yyyy hh:mm)&lt;/option&gt;\
			&lt;/select&gt;\
		&lt;/td&gt;&lt;td width=&quot;50%&quot;&gt;\
			&lt;input type=&quot;text&quot; id=&quot;exportStartDate&quot; onfocus=&quot;this.select()&quot;\
				onchange=&quot;$(\'exportFilterStartBy\').value=\'other\';&quot;&gt;\
		&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
		&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=&quot;exportFilterEnd&quot; value=&quot;1&quot;\
			onclick=&quot;config.macros.exportTiddlers.showFilterFields(this)&quot;&gt; ending date/time&lt;br&gt;\
		&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;&gt;&lt;tr valign=&quot;center&quot;&gt;&lt;td width=&quot;50%&quot;&gt;\
			&lt;select size=1 id=&quot;exportFilterEndBy&quot; \
				onchange=&quot;config.macros.exportTiddlers.showFilterFields(this);&quot;&gt;\
				&lt;option value=&quot;0&quot;&gt;today&lt;/option&gt;\
				&lt;option value=&quot;1&quot;&gt;yesterday&lt;/option&gt;\
				&lt;option value=&quot;7&quot;&gt;a week ago&lt;/option&gt;\
				&lt;option value=&quot;30&quot;&gt;a month ago&lt;/option&gt;\
				&lt;option value=&quot;file&quot;&gt;file date&lt;/option&gt;\
				&lt;option value=&quot;other&quot;&gt;other (mm/dd/yyyy hh:mm)&lt;/option&gt;\
			&lt;/select&gt;\
		&lt;/td&gt;&lt;td width=&quot;50%&quot;&gt;\
			&lt;input type=&quot;text&quot; id=&quot;exportEndDate&quot; onfocus=&quot;this.select()&quot;\
				onchange=&quot;$(\'exportFilterEndBy\').value=\'other\';&quot;&gt;\
		&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;\
		&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=exportFilterTags value=&quot;1&quot;\
			onclick=&quot;config.macros.exportTiddlers.showFilterFields(this)&quot;&gt; match tags&lt;br&gt;\
		&lt;input type=&quot;text&quot; id=&quot;exportTags&quot; onfocus=&quot;this.select()&quot;&gt;\
		&lt;input type=&quot;checkbox&quot; class=&quot;chk&quot; id=exportFilterText value=&quot;1&quot;\
			onclick=&quot;config.macros.exportTiddlers.showFilterFields(this)&quot;&gt; match titles/tiddler text&lt;br&gt;\
		&lt;input type=&quot;text&quot; id=&quot;exportText&quot; onfocus=&quot;this.select()&quot;&gt;\
		&lt;/div&gt; &lt;!--box--&gt;\
		&lt;/div&gt; &lt;!--panel--&gt;\
		&lt;!-- action buttons --&gt;\
		&lt;div style=&quot;text-align:center&quot;&gt;\
		&lt;input type=button class=&quot;btn4&quot; onclick=&quot;config.macros.exportTiddlers.process(this)&quot;\
			id=&quot;exportFilter&quot; value=&quot;apply filter&quot;&gt;\
		&lt;input type=button class=&quot;btn4&quot; onclick=&quot;config.macros.exportTiddlers.process(this)&quot;\
			id=&quot;exportStart&quot; value=&quot;export tiddlers&quot;&gt;\
		&lt;input type=button class=&quot;btn4&quot; onclick=&quot;config.macros.exportTiddlers.process(this)&quot;\
			id=&quot;exportDelete&quot; value=&quot;delete tiddlers&quot;&gt;\
		&lt;input type=button class=&quot;btn4&quot; onclick=&quot;config.macros.exportTiddlers.process(this)&quot;\
			id=&quot;exportClose&quot; value=&quot;close&quot;&gt;\
		&lt;/div&gt;&lt;!--center--&gt;\
	',
	process: function(which) { // process panel control interactions
		// DEBUG alert(which.id);
		var theList=$('exportList'); if (!theList) return;
		var count = 0;
		var total = store.getTiddlers('title').length;
		switch (which.id) {
			case 'exportFilter':
				count=this.filterExportList();
				var panel=$('exportFilterPanel');
				if (count==-1) { panel.style.display='block'; break; }
				$(&quot;exportStart&quot;).disabled=(count==0);
				$(&quot;exportDelete&quot;).disabled=(count==0);
				this.displayStatus(count,total);
				if (count==0) { alert(&quot;No tiddlers were selected&quot;); panel.style.display='block'; }
				break;
			case 'exportStart':
				this.go();
				break;
			case 'exportDelete':
				this.deleteTiddlers();
				break;
			case 'exportHideFilter':
			case 'exportToggleFilter':
				var panel=$('exportFilterPanel')
				panel.style.display=(panel.style.display=='block')?'none':'block';
				break;
			case 'exportSelectChanges':
				var lastmod=new Date(document.lastModified);
				for (var t = 0; t &lt; theList.options.length; t++) {
					if (theList.options[t].value==&quot;&quot;) continue;
					var tiddler=store.getTiddler(theList.options[t].value); if (!tiddler) continue;
					theList.options[t].selected=(tiddler.modified&gt;lastmod);
					count += (tiddler.modified&gt;lastmod)?1:0;
				}
				$(&quot;exportStart&quot;).disabled=(count==0);
				$(&quot;exportDelete&quot;).disabled=(count==0);
				this.displayStatus(count,total);
				if (count==0) alert(&quot;There are no unsaved changes&quot;);
				break;
			case 'exportSelectAll':
				for (var t = 0; t &lt; theList.options.length; t++) {
					if (theList.options[t].value==&quot;&quot;) continue;
					theList.options[t].selected=true;
					count += 1;
				}
				$(&quot;exportStart&quot;).disabled=(count==0);
				$(&quot;exportDelete&quot;).disabled=(count==0);
				this.displayStatus(count,count);
				break;
			case 'exportSelectOpened':
				for (var t = 0; t &lt; theList.options.length; t++) theList.options[t].selected=false;
				var tiddlerDisplay = $(&quot;tiddlerDisplay&quot;); // for TW2.1-
				if (!tiddlerDisplay) tiddlerDisplay = $(&quot;storyDisplay&quot;); // for TW2.2+
				for (var t=0;t&lt;tiddlerDisplay.childNodes.length;t++) {
					var tiddler=tiddlerDisplay.childNodes[t].id.substr(7);
					for (var i = 0; i &lt; theList.options.length; i++) {
						if (theList.options[i].value!=tiddler) continue;
						theList.options[i].selected=true; count++; break;
					}
				}
				$(&quot;exportStart&quot;).disabled=(count==0);
				$(&quot;exportDelete&quot;).disabled=(count==0);
				this.displayStatus(count,total);
				if (count==0) alert(&quot;There are no tiddlers currently opened&quot;);
				break;
			case 'exportSelectRelated':
				// recursively build list of related tiddlers
				function getRelatedTiddlers(tid,tids) {
					var t=store.getTiddler(tid); if (!t || tids.contains(tid)) return tids;
					tids.push(t.title);
					if (!t.linksUpdated) t.changed();
					for (var i=0; i&lt;t.links.length; i++)
						if (t.links[i]!=tid) tids=getRelatedTiddlers(t.links[i],tids);
					return tids;
				}
				// for all currently selected tiddlers, gather up the related tiddlers (including self) and select them as well
				var tids=[];
				for (var i=0; i&lt;theList.options.length; i++)
					if (theList.options[i].selected) tids=getRelatedTiddlers(theList.options[i].value,tids);
				// select related tiddlers (includes original selected tiddlers)
				for (var i=0; i&lt;theList.options.length; i++)
					theList.options[i].selected=tids.contains(theList.options[i].value);
				this.displayStatus(tids.length,total);
				break;
			case 'exportListSmaller':	// decrease current listbox size
				var min=5;
				theList.size-=(theList.size&gt;min)?1:0;
				break;
			case 'exportListLarger':	// increase current listbox size
				var max=(theList.options.length&gt;25)?theList.options.length:25;
				theList.size+=(theList.size&lt;max)?1:0;
				break;
			case 'exportClose':
				$('exportPanel').style.display='none';
				break;
		}
	},
	displayStatus: function(count,total) {
		var txt=this.statusmsg.format([total,total!=1?'s':'',!count?&quot;none&quot;:count==total?&quot;all&quot;:count]);
		clearMessage();	displayMessage(txt);
		return txt;
	},
	refreshList: function(selectedIndex) {
		var theList = $(&quot;exportList&quot;); if (!theList) return;
		// get the sort order
		var sort;
		if (!selectedIndex)   selectedIndex=0;
		if (selectedIndex==0) sort='modified';
		if (selectedIndex==1) sort='title';
		if (selectedIndex==2) sort='modified';
		if (selectedIndex==3) sort='modifier';
		if (selectedIndex==4) sort='tags';

		// unselect headings and count number of tiddlers actually selected
		var count=0;
		for (var t=5; t &lt; theList.options.length; t++) {
			if (!theList.options[t].selected) continue;
			if (theList.options[t].value!=&quot;&quot;)
				count++;
			else { // if heading is selected, deselect it, and then select and count all in section
				theList.options[t].selected=false;
				for ( t++; t&lt;theList.options.length &amp;&amp; theList.options[t].value!=&quot;&quot;; t++) {
					theList.options[t].selected=true;
					count++;
				}
			}
		}

		// disable &quot;export&quot; and &quot;delete&quot; buttons if no tiddlers selected
		$(&quot;exportStart&quot;).disabled=(count==0);
		$(&quot;exportDelete&quot;).disabled=(count==0);

		// show selection count
		var tiddlers = store.getTiddlers('title');
		if (theList.options.length) this.displayStatus(count,tiddlers.length);

		// if a [command] item, reload list... otherwise, no further refresh needed
		if (selectedIndex&gt;4) return;

		// clear current list contents
		while (theList.length &gt; 0) { theList.options[0] = null; }
		// add heading and control items to list
		var i=0;
		var indent=String.fromCharCode(160)+String.fromCharCode(160);
		theList.options[i++]=
			new Option(tiddlers.length+&quot; tiddlers in document&quot;, &quot;&quot;,false,false);
		theList.options[i++]=
			new Option(((sort==&quot;title&quot;   )?&quot;&gt;&quot;:indent)+' [by title]', &quot;&quot;,false,false);
		theList.options[i++]=
			new Option(((sort==&quot;modified&quot;)?&quot;&gt;&quot;:indent)+' [by date]', &quot;&quot;,false,false);
		theList.options[i++]=
			new Option(((sort==&quot;modifier&quot;)?&quot;&gt;&quot;:indent)+' [by author]', &quot;&quot;,false,false);
		theList.options[i++]=
			new Option(((sort==&quot;tags&quot;    )?&quot;&gt;&quot;:indent)+' [by tags]', &quot;&quot;,false,false);

		// output the tiddler list
		switch(sort) {
			case &quot;title&quot;:
				for(var t = 0; t &lt; tiddlers.length; t++)
					theList.options[i++] = new Option(tiddlers[t].title,tiddlers[t].title,false,false);
				break;
			case &quot;modifier&quot;:
			case &quot;modified&quot;:
				var tiddlers = store.getTiddlers(sort);
				// sort descending for newest date first
				tiddlers.sort(function (a,b) {if(a[sort] == b[sort]) return(0); else return (a[sort] &gt; b[sort]) ? -1 : +1; });
				var lastSection = &quot;&quot;;
				for(var t = 0; t &lt; tiddlers.length; t++) {
					var tiddler = tiddlers[t];
					var theSection = &quot;&quot;;
					if (sort==&quot;modified&quot;) theSection=tiddler.modified.toLocaleDateString();
					if (sort==&quot;modifier&quot;) theSection=tiddler.modifier;
					if (theSection != lastSection) {
						theList.options[i++] = new Option(theSection,&quot;&quot;,false,false);
						lastSection = theSection;
					}
					theList.options[i++] = new Option(indent+indent+tiddler.title,tiddler.title,false,false);
				}
				break;
			case &quot;tags&quot;:
				var theTitles = {}; // all tiddler titles, hash indexed by tag value
				var theTags = new Array();
				for(var t=0; t&lt;tiddlers.length; t++) {
					var title=tiddlers[t].title;
					var tags=tiddlers[t].tags;
					if (!tags || !tags.length) {
						if (theTitles[&quot;untagged&quot;]==undefined) { theTags.push(&quot;untagged&quot;); theTitles[&quot;untagged&quot;]=new Array(); }
						theTitles[&quot;untagged&quot;].push(title);
					}
					else for(var s=0; s&lt;tags.length; s++) {
						if (theTitles[tags[s]]==undefined) { theTags.push(tags[s]); theTitles[tags[s]]=new Array(); }
						theTitles[tags[s]].push(title);
					}
				}
				theTags.sort();
				for(var tagindex=0; tagindex&lt;theTags.length; tagindex++) {
					var theTag=theTags[tagindex];
					theList.options[i++]=new Option(theTag,&quot;&quot;,false,false);
					for(var t=0; t&lt;theTitles[theTag].length; t++)
						theList.options[i++]=new Option(indent+indent+theTitles[theTag][t],theTitles[theTag][t],false,false);
				}
				break;
			}
		theList.selectedIndex=selectedIndex; // select current control item
		$(&quot;exportStart&quot;).disabled=true;
		$(&quot;exportDelete&quot;).disabled=true;
		this.displayStatus(0,tiddlers.length);
	},
	askForFilename: function(here) {
		var msg=here.title; // use tooltip as dialog box message
		var path=getLocalPath(document.location.href);
		var slashpos=path.lastIndexOf(&quot;/&quot;); if (slashpos==-1) slashpos=path.lastIndexOf(&quot;\\&quot;); 
		if (slashpos!=-1) path = path.substr(0,slashpos+1); // remove filename from path, leave the trailing slash
		var filetype=$(&quot;exportFormat&quot;).value.toLowerCase();
		var defext='html';
		if (filetype==this.type_TX) defext='txt';
		if (filetype==this.type_NF) defext='xml';
		var file=this.newdefault.replace(/html$/,defext);
		var result=&quot;&quot;;
		if(window.Components) { // moz
			try {
				netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
				var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
				var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
				picker.init(window, msg, nsIFilePicker.modeSave);
				var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
				thispath.initWithPath(path);
				picker.displayDirectory=thispath;
				picker.defaultExtension=defext;
				picker.defaultString=file;
				picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
				if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.persistentDescriptor;
			}
			catch(e) { alert('error during local file access: '+e.toString()) }
		}
		else { // IE
			try { // XPSP2 IE only
				var s = new ActiveXObject('UserAccounts.CommonDialog');
				s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|XML files|*.xml|';
				s.FilterIndex=defext==&quot;txt&quot;?2:&quot;html&quot;?3:&quot;xml&quot;?4:1;
				s.InitialDir=path;
				s.FileName=file;
				if (s.showOpen()) var result=s.FileName;
			}
			catch(e) {  // fallback
				var result=prompt(msg,path+file);
			}
		}
		return result;
	},
	initFilter: function() {
		// start date
		$(&quot;exportFilterStart&quot;).checked=false;
		$(&quot;exportStartDate&quot;).value=&quot;&quot;;
		// end date
		$(&quot;exportFilterEnd&quot;).checked=false;
		$(&quot;exportEndDate&quot;).value=&quot;&quot;;
		// tags
		$(&quot;exportFilterTags&quot;).checked=false;
		$(&quot;exportTags&quot;).value=&quot;&quot;;
		// text
		$(&quot;exportFilterText&quot;).checked=false;
		$(&quot;exportText&quot;).value=&quot;&quot;;
		// show/hide filter input fields
		this.showFilterFields();
	},
	showFilterFields: function(which) {
		var show=$('exportFilterStart').checked;
		$('exportFilterStartBy').style.display=show?&quot;block&quot;:&quot;none&quot;;
		$('exportStartDate').style.display=show?&quot;block&quot;:&quot;none&quot;;
		var val=$('exportFilterStartBy').value;
		$('exportStartDate').value
			=this.getFilterDate(val,'exportStartDate').formatString(this.datetimefmt);
		if (which &amp;&amp; (which.id=='exportFilterStartBy') &amp;&amp; (val=='other'))
			$('exportStartDate').focus();

		var show=$('exportFilterEnd').checked;
		$('exportFilterEndBy').style.display=show?&quot;block&quot;:&quot;none&quot;;
		$('exportEndDate').style.display=show?&quot;block&quot;:&quot;none&quot;;
		var val=$('exportFilterEndBy').value;
		$('exportEndDate').value
			=this.getFilterDate(val,'exportEndDate').formatString(this.datetimefmt);
		 if (which &amp;&amp; (which.id=='exportFilterEndBy') &amp;&amp; (val=='other'))
			$('exportEndDate').focus();

		var show=$('exportFilterTags').checked;
		$('exportTags').style.display=show?&quot;block&quot;:&quot;none&quot;;

		var show=$('exportFilterText').checked;
		$('exportText').style.display=show?&quot;block&quot;:&quot;none&quot;;
	},
	getFilterDate: function(val,id) {
		var result=0;
		switch (val) {
			case 'file':
				result=new Date(document.lastModified);
				break;
			case 'other':
				result=new Date($(id).value);
				break;
			default: // today=0, yesterday=1, one week=7, two weeks=14, a month=31
				var now=new Date(); var tz=now.getTimezoneOffset()*60000; now-=tz;
				var oneday=86400000;
				if (id=='exportStartDate')
					result=new Date((Math.floor(now/oneday)-val)*oneday+tz);
				else
					result=new Date((Math.floor(now/oneday)-val+1)*oneday+tz-1);
				break;
		}
		return result;
	},
	filterExportList: function() {
		var theList  = $(&quot;exportList&quot;); if (!theList) return -1;
		var filterStart=$(&quot;exportFilterStart&quot;).checked;
		var val=$(&quot;exportFilterStartBy&quot;).value;
		var startDate=config.macros.exportTiddlers.getFilterDate(val,'exportStartDate');
		var filterEnd=$(&quot;exportFilterEnd&quot;).checked;
		var val=$(&quot;exportFilterEndBy&quot;).value;
		var endDate=config.macros.exportTiddlers.getFilterDate(val,'exportEndDate');
		var filterTags=$(&quot;exportFilterTags&quot;).checked;
		var tags=$(&quot;exportTags&quot;).value;
		var filterText=$(&quot;exportFilterText&quot;).checked;
		var text=$(&quot;exportText&quot;).value;
		if (!(filterStart||filterEnd||filterTags||filterText)) {
			alert(&quot;Please set the selection filter&quot;);
			$('exportFilterPanel').style.display=&quot;block&quot;;
			return -1;
		}
		if (filterStart&amp;&amp;filterEnd&amp;&amp;(startDate&gt;endDate)) {
			var msg=&quot;starting date/time:\n&quot;
			msg+=startDate.toLocaleString()+&quot;\n&quot;;
			msg+=&quot;is later than ending date/time:\n&quot;
			msg+=endDate.toLocaleString()
			alert(msg);
			return -1;
		}
		// if filter by tags, get list of matching tiddlers
		// use getMatchingTiddlers() (if MatchTagsPlugin is installed) for full boolean expressions
		// otherwise use getTaggedTiddlers() for simple tag matching
		if (filterTags) {
			var fn=store.getMatchingTiddlers||store.getTaggedTiddlers;
			var t=fn.apply(store,[tags]);
			var tagged=[];
			for (var i=0; i&lt;t.length; i++) tagged.push(t[i].title);
		}
		// scan list and select tiddlers that match all applicable criteria
		var total=0;
		var count=0;
		for (var i=0; i&lt;theList.options.length; i++) {
			// get item, skip non-tiddler list items (section headings)
			var opt=theList.options[i]; if (opt.value==&quot;&quot;) continue;
			// get tiddler, skip missing tiddlers (this should NOT happen)
			var tiddler=store.getTiddler(opt.value); if (!tiddler) continue; 
			var sel=true;
			if ( (filterStart &amp;&amp; tiddler.modified&lt;startDate)
			|| (filterEnd &amp;&amp; tiddler.modified&gt;endDate)
			|| (filterTags &amp;&amp; !tagged.contains(tiddler.title))
			|| (filterText &amp;&amp; (tiddler.text.indexOf(text)==-1) &amp;&amp; (tiddler.title.indexOf(text)==-1)))
				sel=false;
			opt.selected=sel;
			count+=sel?1:0;
			total++;
		}
		return count;
	},
	deleteTiddlers: function() {
		var list=$(&quot;exportList&quot;); if (!list) return;
		var tids=[];
		for (i=0;i&lt;list.length;i++)
			if (list.options[i].selected &amp;&amp; list.options[i].value.length)
				tids.push(list.options[i].value);
		if (!confirm(&quot;Are you sure you want to delete these tiddlers:\n\n&quot;+tids.join(', '))) return;
		store.suspendNotifications();
		for (t=0;t&lt;tids.length;t++) {
			var tid=store.getTiddler(tids[t]); if (!tid) continue;
			var msg=&quot;'&quot;+tid.title+&quot;' is tagged with 'systemConfig'.\n\n&quot;;
			msg+=&quot;Removing this tiddler may cause unexpected results.  Are you sure?&quot;
			if (tid.tags.contains(&quot;systemConfig&quot;) &amp;&amp; !confirm(msg)) continue;
			store.removeTiddler(tid.title);
			story.closeTiddler(tid.title);
		}
		store.resumeNotifications();
		alert(tids.length+&quot; tiddlers deleted&quot;);
		this.refreshList(0); // reload listbox
		store.notifyAll(); // update page display
	},
	go: function() {
		if (window.location.protocol!=&quot;file:&quot;) // make sure we are local
			{ displayMessage(config.messages.notFileUrlError); return; }
		// get selected tidders, target filename, target type, and notes
		var list=$(&quot;exportList&quot;); if (!list) return;
		var tids=[]; for (var i=0; i&lt;list.options.length; i++) {
			var opt=list.options[i]; if (!opt.selected||!opt.value.length) continue;
			var tid=store.getTiddler(opt.value); if (!tid) continue;
			tids.push(tid);
		}
		if (!tids.length) return; // no tiddlers selected
		var target = $(&quot;exportFilename&quot;).value.trim();
		if (!target.length) {
			displayMessage(&quot;A local target path/filename is required&quot;,target);
			return;
		}
		var filetype = $(&quot;exportFormat&quot;).value.toLowerCase();
		var notes=$(&quot;exportNotes&quot;).value.replace(/\n/g,&quot;&lt;br&gt;&quot;);
		var total={val:0};
		var out=this.assembleFile(target,filetype,tids,notes,total);
		var link=&quot;file:///&quot;+target.replace(/\\/g,'/');
		var ok=saveFile(target,out);
		displayMessage((ok?this.okmsg:this.failmsg).format([total.val,target]),link);
	},
	plainTextHeader:
		 '// Source'+':\n//\t%0\n'
		+'// Title:\n//\t%1\n'
		+'// Subtitle:\n//\t%2\n'
		+'// Created:\n//\t%3 by %4\n'
		+'// Application:\n//\tTiddlyWiki %5 / %6 %7\n',
	plainTextTiddler:
		'\n// ----- %0 (by %1 on %2) -----\n\n%3',
	plainTextFooter:
		'',
	newsFeedHeader:
		 '&lt;'+'?xml version=&quot;1.0&quot;?'+'&gt;\n'
		+'&lt;rss version=&quot;2.0&quot;&gt;\n'
		+'&lt;channel&gt;\n'
		+'&lt;title&gt;%1&lt;/title&gt;\n'
		+'&lt;link&gt;%0&lt;/link&gt;\n'
		+'&lt;description&gt;%2&lt;/description&gt;\n'
		+'&lt;language&gt;en-us&lt;/language&gt;\n'
		+'&lt;copyright&gt;Copyright '+(new Date().getFullYear())+' %4&lt;/copyright&gt;\n'
		+'&lt;pubDate&gt;%3&lt;/pubDate&gt;\n'
		+'&lt;lastBuildDate&gt;%3&lt;/lastBuildDate&gt;\n'
		+'&lt;docs&gt;http://blogs.law.harvard.edu/tech/rss&lt;/docs&gt;\n'
		+'&lt;generator&gt;TiddlyWiki %5 / %6 %7&lt;/generator&gt;\n',
	newsFeedTiddler:
		'\n%0\n',
	newsFeedFooter:
		'&lt;/channel&gt;&lt;/rss&gt;',
	pureStoreHeader:
		 '&lt;html&gt;&lt;body&gt;'
		+'&lt;style type=&quot;text/css&quot;&gt;'
		+'	#storeArea {display:block;margin:1em;}'
		+'	#storeArea div {padding:0.5em;margin:1em;border:2px solid black;height:10em;overflow:auto;}'
		+'	#pureStoreHeading {width:100%;text-align:left;background-color:#eeeeee;padding:1em;}'
		+'&lt;/style&gt;'
		+'&lt;div id=&quot;pureStoreHeading&quot;&gt;'
		+'	TiddlyWiki &quot;PureStore&quot; export file&lt;br&gt;'
		+'	Source'+': &lt;b&gt;%0&lt;/b&gt;&lt;br&gt;'
		+'	Title: &lt;b&gt;%1&lt;/b&gt;&lt;br&gt;'
		+'	Subtitle: &lt;b&gt;%2&lt;/b&gt;&lt;br&gt;'
		+'	Created: &lt;b&gt;%3&lt;/b&gt; by &lt;b&gt;%4&lt;/b&gt;&lt;br&gt;'
		+'	TiddlyWiki %5 / %6 %7&lt;br&gt;'
		+'	Notes:&lt;hr&gt;&lt;pre&gt;%8&lt;/pre&gt;'
		+'&lt;/div&gt;'
		+'&lt;div id=&quot;storeArea&quot;&gt;',
	pureStoreTiddler:
		'%0\n%1',
	pureStoreFooter:
		'&lt;/div&gt;&lt;!--POST-BODY-START--&gt;\n&lt;!--POST-BODY-END--&gt;&lt;/body&gt;&lt;/html&gt;',
	assembleFile: function(target,filetype,tids,notes,total) {
		var revised=&quot;&quot;;
		var now = new Date().toLocaleString();
		var src=convertUnicodeToUTF8(document.location.href);
		var title = convertUnicodeToUTF8(wikifyPlain(&quot;SiteTitle&quot;).htmlEncode());
		var subtitle = convertUnicodeToUTF8(wikifyPlain(&quot;SiteSubtitle&quot;).htmlEncode());
		var user = convertUnicodeToUTF8(config.options.txtUserName.htmlEncode());
		var twver = version.major+&quot;.&quot;+version.minor+&quot;.&quot;+version.revision;
		var v=version.extensions.ExportTiddlersPlugin; var pver = v.major+&quot;.&quot;+v.minor+&quot;.&quot;+v.revision;
		var headerargs=[src,title,subtitle,now,user,twver,&quot;ExportTiddlersPlugin&quot;,pver,notes];
		switch (filetype) {
			case this.type_TX: // plain text
				var header=this.plainTextHeader.format(headerargs);
				var footer=this.plainTextFooter;
				break;
			case this.type_NF: // news feed (XML)
				headerargs[0]=store.getTiddlerText(&quot;SiteUrl&quot;,&quot;&quot;);
				var header=this.newsFeedHeader.format(headerargs);
				var footer=this.newsFeedFooter;
				break;
			case this.type_PS: // PureStore (no code)
				var header=this.pureStoreHeader.format(headerargs);
				var footer=this.pureStoreFooter;
				break;
			case this.type_TW: // full TiddlyWiki
			default:
				var currPath=getLocalPath(window.location.href);
				var original=loadFile(currPath);
				if (!original) { displayMessage(config.messages.cantSaveError); return; }
				var posDiv = locateStoreArea(original);
				if (!posDiv) { displayMessage(config.messages.invalidFileError.format([currPath])); return; }
				var header = original.substr(0,posDiv[0]+startSaveArea.length)+&quot;\n&quot;;
				var footer = &quot;\n&quot;+original.substr(posDiv[1]);
				break;
		}
		var out=this.getData(target,filetype,tids);
		var revised = header+convertUnicodeToUTF8(out.join(&quot;\n&quot;))+footer;
		// if full TW, insert page title and language attr, and reset all MARKUP blocks...
		if (filetype==this.type_TW) {
			var newSiteTitle = convertUnicodeToUTF8(getPageTitle()).htmlEncode();
			revised = revised.replaceChunk(&quot;&lt;title&quot;+&quot;&gt;&quot;,&quot;&lt;/title&quot;+&quot;&gt;&quot;,&quot; &quot; + newSiteTitle + &quot; &quot;);
			revised = updateLanguageAttribute(revised);
			revised = updateMarkupBlock(revised,&quot;PRE-HEAD&quot;);
			revised = updateMarkupBlock(revised,&quot;POST-HEAD&quot;);
			revised = updateMarkupBlock(revised,&quot;PRE-BODY&quot;);
			revised = updateMarkupBlock(revised,&quot;POST-SCRIPT&quot;);
		}
		total.val=out.length;
		return revised;
	},
	formatItem: function(s,f,t,u) {
		if (f==this.type_TW)
			var r=s.getSaver().externalizeTiddler(s,t);
		if (f==this.type_PS)
			var r=config.macros.exportTiddlers.pureStoreTiddler.format([t.title,s.getSaver().externalizeTiddler(s,t)]);
		if (f==this.type_NF)
			var r=this.newsFeedTiddler.format([t.saveToRss(u)]);
		if (f==this.type_TX)
			var r=this.plainTextTiddler.format([t.title,t.modifier,t.modified.toLocaleString(),t.text]);
		return r||&quot;&quot;;
	},
	getData: function(target,filetype,tids) {
		// output selected tiddlers and gather list of titles (for use with merge)
		var out=[]; var titles=[];
		var url=store.getTiddlerText(&quot;SiteUrl&quot;,&quot;&quot;);
		for (var i=0; i&lt;tids.length; i++) {
			out.push(this.formatItem(store,filetype,tids[i],url));
			titles.push(tids[i].title);
		}
		// if TW or PureStore format, ask to merge with existing tiddlers (if any)
		if (filetype==this.type_TW || filetype==this.type_PS) {
			var text=loadFile(target);
			if (text &amp;&amp; text.length) {
				var remoteStore=new TiddlyWiki();
				if (remoteStore.importTiddlyWiki(text) &amp;&amp; confirm(this.mergeprompt.format([target]))) {
					var existing=remoteStore.getTiddlers(&quot;title&quot;);
					for (var i=0; i&lt;existing.length; i++)
						if (!titles.contains(existing[i].title))
							out.push(this.formatItem(remoteStore,filetype,existing[i],url));
					displayMessage(this.mergestatus.format([tids.length,out.length-tids.length]));
				}
			}
		}
		return out;
	}
}
//}}}</pre>
</div>
<div title="Features" modifier="YBA" created="200809111403" modified="200809171253">
<pre>Lister les fonctionnalités principales, sans rentrer dans le détail</pre>
</div>
<div title="Fini" modifier="YBA" created="200809111408" modified="200809151606" changecount="2">
<pre>* Documenté (en interne dans TW)
* Testé (avec Sélénium + tests unitaires dans TW)
* Refactoré
* Le moins de dépendances possibles
* Le moins de code spécifique possible
* ''avec des données d'exemple''</pre>
</div>
<div title="ForEachTiddlerMacro" modifier="YBA" created="200809220901" tags="excludeLists" changecount="1">
<pre>//~~(Part of the [[ForEachTiddlerPlugin]])~~//

Create customizable lists, tables etc. for your selections of tiddlers. Specify the tiddlers to include and their order through a powerful language.

''Syntax:'' 
|&gt;|{{{&lt;&lt;}}}''forEachTiddler'' [''in'' //tiddlyWikiPath//] [''where'' //whereCondition//] [''sortBy'' //sortExpression// [''ascending'' //or// ''descending'']] [''script'' //scriptText//] [//action// [//actionParameters//]]{{{&gt;&gt;}}}|
|//tiddlyWikiPath//|The filepath to the TiddlyWiki the macro should work on. When missing the current TiddlyWiki is used.|
|//whereCondition//|(quoted) JavaScript boolean expression. May refer to the build-in variables {{{tiddler}}} and {{{context}}}.|
|//sortExpression//|(quoted) JavaScript expression returning &quot;comparable&quot; objects (using '{{{&lt;}}}','{{{&gt;}}}','{{{==}}}'. May refer to the build-in variables {{{tiddler}}} and {{{context}}}.|
|//scriptText//|(quoted) JavaScript text. Typically defines JavaScript functions that are called by the various JavaScript expressions (whereClause, sortClause, action arguments,...)|
|//action//|The action that should be performed on every selected tiddler, in the given order. By default the actions [[addToList|AddToListAction]] and [[write|WriteAction]] are supported. When no action is specified [[addToList|AddToListAction]] is used.|
|//actionParameters//|(action specific) parameters the action may refer while processing the tiddlers (see action descriptions for details). &lt;&lt;tiddler [[JavaScript in actionParameters]]&gt;&gt;|
|&gt;|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|


''Using JavaScript''

To give you a lot of flexibility the [[ForEachTiddlerMacro]] uses JavaScript in its arguments. Even if you are not that familiar with JavaScript you may find forEachTiddler useful. Just have a look at the various ready-to-use [[ForEachTiddlerExamples]] and adapt them to your needs.

''The Elements of the Macro''

The arguments of the ForEachTiddlerMacro consist of multiple parts, each of them being optional.

&lt;&lt;slider chkFETInClause [[inClause]] &quot;inClause&quot; &quot;inClause&quot;&gt;&gt;
&lt;&lt;slider chkFETWhereClause [[whereClause]] &quot;whereClause&quot; &quot;whereClause&quot;&gt;&gt;
&lt;&lt;slider chkFETSortClause [[sortClause]] &quot;sortClause&quot; &quot;sortClause&quot;&gt;&gt;
&lt;&lt;slider chkFETScriptClause [[scriptClause]] &quot;scriptClause&quot; &quot;scriptClause&quot;&gt;&gt;
&lt;&lt;slider chkFETActions [[Action Specification]] &quot;Action Specification&quot; &quot;Action Specification&quot;&gt;&gt;

''Using Macros and &quot;&gt;&quot; inside the forEachTiddler Macro''

You may use other macro calls into the expression, especially in the actionParameters. To avoid that the {{{&gt;&gt;}}} of such a macro call is misinterpreted as the end of the {{{&lt;&lt;forEachTiddler...&gt;&gt;}}} macro you must escape the {{{&gt;&gt;}}} of the inner macro with {{{$))}}} E.g. if you want to use {{{&lt;&lt;tiddler ...&gt;&gt;}}} inside the {{{forEachTiddler}}} macro you have to write {{{&lt;&lt;tiddler ...$))}}}.

In addition it is necessary to escape single {{{&gt;}}} with the text {{{$)}}}.

''Using {{{&lt;&lt;tiddler ... with: ...&gt;&gt;}}} to re-use ForEachTiddler definitions''

Sometimes you may want to use a certain ForEachTiddler definition in slight variations. E.g. you may want to list either the tiddlers tagged with &quot;ToDo&quot; and in the other case with &quot;Done&quot;. To do so you may use &quot;Tiddler parameters&quot;. Here an example:

Replace the variable part of the ForEachTiddler definition with $1 ($2,... $9 are supported). E.g. you may create the tiddler &quot;ListTaggedTiddlers&quot; like this
{{{
&lt;&lt;forEachTiddler 
 where 
 'tiddler.tags.contains(&quot;$1&quot;)'
&gt;&gt;
}}}

Now you can use the ListTaggedTiddlers for various specific tags, using the {{{&lt;&lt;tiddler ...&gt;&gt;}}} macro:
{{{
&lt;&lt;tiddler ListTaggedTiddlers with: &quot;systemConfig&quot;&gt;&gt;
}}}
{{{
&lt;&lt;tiddler ListTaggedTiddlers with: &quot;Plugin&quot;&gt;&gt;
}}}


See also [[ForEachTiddlerExamples]].</pre>
</div>
<div title="ForEachTiddlerPlugin" modifier="YBA" created="200809091006" modified="200809220808" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|''Name:''|ForEachTiddlerPlugin|
|''Version:''|1.0.8 (2007-04-12)|
|''Source:''|http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''Licence:''|[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]|
|''Copyright:''|&amp;copy; 2005-2007 [[abego Software|http://www.abego-software.de]]|
|''TiddlyWiki:''|1.2.38+, 2.0|
|''Browser:''|Firefox 1.0.4+; Firefox 1.5; InternetExplorer 6.0|
!Description

Create customizable lists, tables etc. for your selections of tiddlers. Specify the tiddlers to include and their order through a powerful language.

''Syntax:'' 
|&gt;|{{{&lt;&lt;}}}''forEachTiddler'' [''in'' //tiddlyWikiPath//] [''where'' //whereCondition//] [''sortBy'' //sortExpression// [''ascending'' //or// ''descending'']] [''script'' //scriptText//] [//action// [//actionParameters//]]{{{&gt;&gt;}}}|
|//tiddlyWikiPath//|The filepath to the TiddlyWiki the macro should work on. When missing the current TiddlyWiki is used.|
|//whereCondition//|(quoted) JavaScript boolean expression. May refer to the build-in variables {{{tiddler}}} and  {{{context}}}.|
|//sortExpression//|(quoted) JavaScript expression returning &quot;comparable&quot; objects (using '{{{&lt;}}}','{{{&gt;}}}','{{{==}}}'. May refer to the build-in variables {{{tiddler}}} and  {{{context}}}.|
|//scriptText//|(quoted) JavaScript text. Typically defines JavaScript functions that are called by the various JavaScript expressions (whereClause, sortClause, action arguments,...)|
|//action//|The action that should be performed on every selected tiddler, in the given order. By default the actions [[addToList|AddToListAction]] and [[write|WriteAction]] are supported. When no action is specified [[addToList|AddToListAction]]  is used.|
|//actionParameters//|(action specific) parameters the action may refer while processing the tiddlers (see action descriptions for details). &lt;&lt;tiddler [[JavaScript in actionParameters]]&gt;&gt;|
|&gt;|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|

See details see [[ForEachTiddlerMacro]] and [[ForEachTiddlerExamples]].

!Revision history
* v1.0.8 (2007-04-12)
** Adapted to latest TiddlyWiki 2.2 Beta importTiddlyWiki API (introduced with changeset 2004). TiddlyWiki 2.2 Beta builds prior to changeset 2004 are no longer supported (but TiddlyWiki 2.1 and earlier, of cause)
* v1.0.7 (2007-03-28)
** Also support &quot;pre&quot; formatted TiddlyWikis (introduced with TW 2.2) (when using &quot;in&quot; clause to work on external tiddlers)
* v1.0.6 (2006-09-16)
** Context provides &quot;viewerTiddler&quot;, i.e. the tiddler used to view the macro. Most times this is equal to the &quot;inTiddler&quot;, but when using the &quot;tiddler&quot; macro both may be different.
** Support &quot;begin&quot;, &quot;end&quot; and &quot;none&quot; expressions in &quot;write&quot; action
* v1.0.5 (2006-02-05)
** Pass tiddler containing the macro with wikify, context object also holds reference to tiddler containing the macro (&quot;inTiddler&quot;). Thanks to SimonBaird.
** Support Firefox 1.5.0.1
** Internal
*** Make &quot;JSLint&quot; conform
*** &quot;Only install once&quot;
* v1.0.4 (2006-01-06)
** Support TiddlyWiki 2.0
* v1.0.3 (2005-12-22)
** Features: 
*** Write output to a file supports multi-byte environments (Thanks to Bram Chen) 
*** Provide API to access the forEachTiddler functionality directly through JavaScript (see getTiddlers and performMacro)
** Enhancements:
*** Improved error messages on InternetExplorer.
* v1.0.2 (2005-12-10)
** Features: 
*** context object also holds reference to store (TiddlyWiki)
** Fixed Bugs: 
*** ForEachTiddler 1.0.1 has broken support on win32 Opera 8.51 (Thanks to BrunoSabin for reporting)
* v1.0.1 (2005-12-08)
** Features: 
*** Access tiddlers stored in separated TiddlyWikis through the &quot;in&quot; option. I.e. you are no longer limited to only work on the &quot;current TiddlyWiki&quot;.
*** Write output to an external file using the &quot;toFile&quot; option of the &quot;write&quot; action. With this option you may write your customized tiddler exports.
*** Use the &quot;script&quot; section to define &quot;helper&quot; JavaScript functions etc. to be used in the various JavaScript expressions (whereClause, sortClause, action arguments,...).
*** Access and store context information for the current forEachTiddler invocation (through the build-in &quot;context&quot; object) .
*** Improved script evaluation (for where/sort clause and write scripts).
* v1.0.0 (2005-11-20)
** initial version

!Code
***/
//{{{

	
//============================================================================
//============================================================================
//		   ForEachTiddlerPlugin
//============================================================================
//============================================================================

// Only install once
if (!version.extensions.ForEachTiddlerPlugin) {

if (!window.abego) window.abego = {};

version.extensions.ForEachTiddlerPlugin = {
	major: 1, minor: 0, revision: 8, 
	date: new Date(2007,3,12), 
	source: &quot;http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin&quot;,
	licence: &quot;[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]&quot;,
	copyright: &quot;Copyright (c) abego Software GmbH, 2005-2007 (www.abego-software.de)&quot;
};

// For backward compatibility with TW 1.2.x
//
if (!TiddlyWiki.prototype.forEachTiddler) {
	TiddlyWiki.prototype.forEachTiddler = function(callback) {
		for(var t in this.tiddlers) {
			callback.call(this,t,this.tiddlers[t]);
		}
	};
}

//============================================================================
// forEachTiddler Macro
//============================================================================

version.extensions.forEachTiddler = {
	major: 1, minor: 0, revision: 8, date: new Date(2007,3,12), provider: &quot;http://tiddlywiki.abego-software.de&quot;};

// ---------------------------------------------------------------------------
// Configurations and constants 
// ---------------------------------------------------------------------------

config.macros.forEachTiddler = {
	 // Standard Properties
	 label: &quot;forEachTiddler&quot;,
	 prompt: &quot;Perform actions on a (sorted) selection of tiddlers&quot;,

	 // actions
	 actions: {
		 addToList: {},
		 write: {}
	 }
};

// ---------------------------------------------------------------------------
//  The forEachTiddler Macro Handler 
// ---------------------------------------------------------------------------

config.macros.forEachTiddler.getContainingTiddler = function(e) {
	while(e &amp;&amp; !hasClass(e,&quot;tiddler&quot;))
		e = e.parentNode;
	var title = e ? e.getAttribute(&quot;tiddler&quot;) : null; 
	return title ? store.getTiddler(title) : null;
};

config.macros.forEachTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	// config.macros.forEachTiddler.traceMacroCall(place,macroName,params,wikifier,paramString,tiddler);

	if (!tiddler) tiddler = config.macros.forEachTiddler.getContainingTiddler(place);
	// --- Parsing ------------------------------------------

	var i = 0; // index running over the params
	// Parse the &quot;in&quot; clause
	var tiddlyWikiPath = undefined;
	if ((i &lt; params.length) &amp;&amp; params[i] == &quot;in&quot;) {
		i++;
		if (i &gt;= params.length) {
			this.handleError(place, &quot;TiddlyWiki path expected behind 'in'.&quot;);
			return;
		}
		tiddlyWikiPath = this.paramEncode((i &lt; params.length) ? params[i] : &quot;&quot;);
		i++;
	}

	// Parse the where clause
	var whereClause =&quot;true&quot;;
	if ((i &lt; params.length) &amp;&amp; params[i] == &quot;where&quot;) {
		i++;
		whereClause = this.paramEncode((i &lt; params.length) ? params[i] : &quot;&quot;);
		i++;
	}

	// Parse the sort stuff
	var sortClause = null;
	var sortAscending = true; 
	if ((i &lt; params.length) &amp;&amp; params[i] == &quot;sortBy&quot;) {
		i++;
		if (i &gt;= params.length) {
			this.handleError(place, &quot;sortClause missing behind 'sortBy'.&quot;);
			return;
		}
		sortClause = this.paramEncode(params[i]);
		i++;

		if ((i &lt; params.length) &amp;&amp; (params[i] == &quot;ascending&quot; || params[i] == &quot;descending&quot;)) {
			 sortAscending = params[i] == &quot;ascending&quot;;
			 i++;
		}
	}

	// Parse the script
	var scriptText = null;
	if ((i &lt; params.length) &amp;&amp; params[i] == &quot;script&quot;) {
		i++;
		scriptText = this.paramEncode((i &lt; params.length) ? params[i] : &quot;&quot;);
		i++;
	}

	// Parse the action. 
	// When we are already at the end use the default action
	var actionName = &quot;addToList&quot;;
	if (i &lt; params.length) {
	   if (!config.macros.forEachTiddler.actions[params[i]]) {
			this.handleError(place, &quot;Unknown action '&quot;+params[i]+&quot;'.&quot;);
			return;
		} else {
			actionName = params[i]; 
			i++;
		}
	} 
	
	// Get the action parameter
	// (the parsing is done inside the individual action implementation.)
	var actionParameter = params.slice(i);


	// --- Processing ------------------------------------------
	try {
		this.performMacro({
				place: place, 
				inTiddler: tiddler,
				whereClause: whereClause, 
				sortClause: sortClause, 
				sortAscending: sortAscending, 
				actionName: actionName, 
				actionParameter: actionParameter, 
				scriptText: scriptText, 
				tiddlyWikiPath: tiddlyWikiPath});

	} catch (e) {
		this.handleError(place, e);
	}
};

// Returns an object with properties &quot;tiddlers&quot; and &quot;context&quot;.
// tiddlers holds the (sorted) tiddlers selected by the parameter,
// context the context of the execution of the macro.
//
// The action is not yet performed.
//
// @parameter see performMacro
//
config.macros.forEachTiddler.getTiddlersAndContext = function(parameter) {

	var context = config.macros.forEachTiddler.createContext(parameter.place, parameter.whereClause, parameter.sortClause, parameter.sortAscending, parameter.actionName, parameter.actionParameter, parameter.scriptText, parameter.tiddlyWikiPath, parameter.inTiddler);

	var tiddlyWiki = parameter.tiddlyWikiPath ? this.loadTiddlyWiki(parameter.tiddlyWikiPath) : store;
	context[&quot;tiddlyWiki&quot;] = tiddlyWiki;
	
	// Get the tiddlers, as defined by the whereClause
	var tiddlers = this.findTiddlers(parameter.whereClause, context, tiddlyWiki);
	context[&quot;tiddlers&quot;] = tiddlers;

	// Sort the tiddlers, when sorting is required.
	if (parameter.sortClause) {
		this.sortTiddlers(tiddlers, parameter.sortClause, parameter.sortAscending, context);
	}

	return {tiddlers: tiddlers, context: context};
};

// Returns the (sorted) tiddlers selected by the parameter.
//
// The action is not yet performed.
//
// @parameter see performMacro
//
config.macros.forEachTiddler.getTiddlers = function(parameter) {
	return this.getTiddlersAndContext(parameter).tiddlers;
};

// Performs the macros with the given parameter.
//
// @param parameter holds the parameter of the macro as separate properties.
//				  The following properties are supported:
//
//						place
//						whereClause
//						sortClause
//						sortAscending
//						actionName
//						actionParameter
//						scriptText
//						tiddlyWikiPath
//
//					All properties are optional. 
//					For most actions the place property must be defined.
//
config.macros.forEachTiddler.performMacro = function(parameter) {
	var tiddlersAndContext = this.getTiddlersAndContext(parameter);

	// Perform the action
	var actionName = parameter.actionName ? parameter.actionName : &quot;addToList&quot;;
	var action = config.macros.forEachTiddler.actions[actionName];
	if (!action) {
		this.handleError(parameter.place, &quot;Unknown action '&quot;+actionName+&quot;'.&quot;);
		return;
	}

	var actionHandler = action.handler;
	actionHandler(parameter.place, tiddlersAndContext.tiddlers, parameter.actionParameter, tiddlersAndContext.context);
};

// ---------------------------------------------------------------------------
//  The actions 
// ---------------------------------------------------------------------------

// Internal.
//
// --- The addToList Action -----------------------------------------------
//
config.macros.forEachTiddler.actions.addToList.handler = function(place, tiddlers, parameter, context) {
	// Parse the parameter
	var p = 0;

	// Check for extra parameters
	if (parameter.length &gt; p) {
		config.macros.forEachTiddler.createExtraParameterErrorElement(place, &quot;addToList&quot;, parameter, p);
		return;
	}

	// Perform the action.
	var list = document.createElement(&quot;ul&quot;);
	place.appendChild(list);
	for (var i = 0; i &lt; tiddlers.length; i++) {
		var tiddler = tiddlers[i];
		var listItem = document.createElement(&quot;li&quot;);
		list.appendChild(listItem);
		createTiddlyLink(listItem, tiddler.title, true);
	}
};

abego.parseNamedParameter = function(name, parameter, i) {
	var beginExpression = null;
	if ((i &lt; parameter.length) &amp;&amp; parameter[i] == name) {
		i++;
		if (i &gt;= parameter.length) {
			throw &quot;Missing text behind '%0'&quot;.format([name]);
		}
		
		return config.macros.forEachTiddler.paramEncode(parameter[i]);
	}
	return null;
}

// Internal.
//
// --- The write Action ---------------------------------------------------
//
config.macros.forEachTiddler.actions.write.handler = function(place, tiddlers, parameter, context) {
	// Parse the parameter
	var p = 0;
	if (p &gt;= parameter.length) {
		this.handleError(place, &quot;Missing expression behind 'write'.&quot;);
		return;
	}

	var textExpression = config.macros.forEachTiddler.paramEncode(parameter[p]);
	p++;

	// Parse the &quot;begin&quot; option
	var beginExpression = abego.parseNamedParameter(&quot;begin&quot;, parameter, p);
	if (beginExpression !== null) 
		p += 2;
	var endExpression = abego.parseNamedParameter(&quot;end&quot;, parameter, p);
	if (endExpression !== null) 
		p += 2;
	var noneExpression = abego.parseNamedParameter(&quot;none&quot;, parameter, p);
	if (noneExpression !== null) 
		p += 2;

	// Parse the &quot;toFile&quot; option
	var filename = null;
	var lineSeparator = undefined;
	if ((p &lt; parameter.length) &amp;&amp; parameter[p] == &quot;toFile&quot;) {
		p++;
		if (p &gt;= parameter.length) {
			this.handleError(place, &quot;Filename expected behind 'toFile' of 'write' action.&quot;);
			return;
		}
		
		filename = config.macros.forEachTiddler.getLocalPath(config.macros.forEachTiddler.paramEncode(parameter[p]));
		p++;
		if ((p &lt; parameter.length) &amp;&amp; parameter[p] == &quot;withLineSeparator&quot;) {
			p++;
			if (p &gt;= parameter.length) {
				this.handleError(place, &quot;Line separator text expected behind 'withLineSeparator' of 'write' action.&quot;);
				return;
			}
			lineSeparator = config.macros.forEachTiddler.paramEncode(parameter[p]);
			p++;
		}
	}
	
	// Check for extra parameters
	if (parameter.length &gt; p) {
		config.macros.forEachTiddler.createExtraParameterErrorElement(place, &quot;write&quot;, parameter, p);
		return;
	}

	// Perform the action.
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(textExpression, context);
	var count = tiddlers.length;
	var text = &quot;&quot;;
	if (count &gt; 0 &amp;&amp; beginExpression)
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(beginExpression, context)(undefined, context, count, undefined);
	
	for (var i = 0; i &lt; count; i++) {
		var tiddler = tiddlers[i];
		text += func(tiddler, context, count, i);
	}
	
	if (count &gt; 0 &amp;&amp; endExpression)
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(endExpression, context)(undefined, context, count, undefined);

	if (count == 0 &amp;&amp; noneExpression) 
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(noneExpression, context)(undefined, context, count, undefined);
		

	if (filename) {
		if (lineSeparator !== undefined) {
			lineSeparator = lineSeparator.replace(/\\n/mg, &quot;\n&quot;).replace(/\\r/mg, &quot;\r&quot;);
			text = text.replace(/\n/mg,lineSeparator);
		}
		saveFile(filename, convertUnicodeToUTF8(text));
	} else {
		var wrapper = createTiddlyElement(place, &quot;span&quot;);
		wikify(text, wrapper, null/* highlightRegExp */, context.inTiddler);
	}
};


// ---------------------------------------------------------------------------
//  Helpers
// ---------------------------------------------------------------------------

// Internal.
//
config.macros.forEachTiddler.createContext = function(placeParam, whereClauseParam, sortClauseParam, sortAscendingParam, actionNameParam, actionParameterParam, scriptText, tiddlyWikiPathParam, inTiddlerParam) {
	return {
		place : placeParam, 
		whereClause : whereClauseParam, 
		sortClause : sortClauseParam, 
		sortAscending : sortAscendingParam, 
		script : scriptText,
		actionName : actionNameParam, 
		actionParameter : actionParameterParam,
		tiddlyWikiPath : tiddlyWikiPathParam,
		inTiddler : inTiddlerParam, // the tiddler containing the &lt;&lt;forEachTiddler ...&gt;&gt; macro call.
		viewerTiddler : config.macros.forEachTiddler.getContainingTiddler(placeParam) // the tiddler showing the forEachTiddler result
	};
};

// Internal.
//
// Returns a TiddlyWiki with the tiddlers loaded from the TiddlyWiki of 
// the given path.
//
config.macros.forEachTiddler.loadTiddlyWiki = function(path, idPrefix) {
	if (!idPrefix) {
		idPrefix = &quot;store&quot;;
	}
	var lenPrefix = idPrefix.length;
	
	// Read the content of the given file
	var content = loadFile(this.getLocalPath(path));
	if(content === null) {
		throw &quot;TiddlyWiki '&quot;+path+&quot;' not found.&quot;;
	}
	
	var tiddlyWiki = new TiddlyWiki();

	// Starting with TW 2.2 there is a helper function to import the tiddlers
	if (tiddlyWiki.importTiddlyWiki) {
		if (!tiddlyWiki.importTiddlyWiki(content))
			throw &quot;File '&quot;+path+&quot;' is not a TiddlyWiki.&quot;;
		tiddlyWiki.dirty = false;
		return tiddlyWiki;
	}
	
	// The legacy code, for TW &lt; 2.2
	
	// Locate the storeArea div's
	var posOpeningDiv = content.indexOf(startSaveArea);
	var posClosingDiv = content.lastIndexOf(endSaveArea);
	if((posOpeningDiv == -1) || (posClosingDiv == -1)) {
		throw &quot;File '&quot;+path+&quot;' is not a TiddlyWiki.&quot;;
	}
	var storageText = content.substr(posOpeningDiv + startSaveArea.length, posClosingDiv);
	
	// Create a &quot;div&quot; element that contains the storage text
	var myStorageDiv = document.createElement(&quot;div&quot;);
	myStorageDiv.innerHTML = storageText;
	myStorageDiv.normalize();
	
	// Create all tiddlers in a new TiddlyWiki
	// (following code is modified copy of TiddlyWiki.prototype.loadFromDiv)
	var store = myStorageDiv.childNodes;
	for(var t = 0; t &lt; store.length; t++) {
		var e = store[t];
		var title = null;
		if(e.getAttribute)
			title = e.getAttribute(&quot;tiddler&quot;);
		if(!title &amp;&amp; e.id &amp;&amp; e.id.substr(0,lenPrefix) == idPrefix)
			title = e.id.substr(lenPrefix);
		if(title &amp;&amp; title !== &quot;&quot;) {
			var tiddler = tiddlyWiki.createTiddler(title);
			tiddler.loadFromDiv(e,title);
		}
	}
	tiddlyWiki.dirty = false;

	return tiddlyWiki;
};


	
// Internal.
//
// Returns a function that has a function body returning the given javaScriptExpression.
// The function has the parameters:
// 
//	 (tiddler, context, count, index)
//
config.macros.forEachTiddler.getEvalTiddlerFunction = function (javaScriptExpression, context) {
	var script = context[&quot;script&quot;];
	var functionText = &quot;var theFunction = function(tiddler, context, count, index) { return &quot;+javaScriptExpression+&quot;}&quot;;
	var fullText = (script ? script+&quot;;&quot; : &quot;&quot;)+functionText+&quot;;theFunction;&quot;;
	return eval(fullText);
};

// Internal.
//
config.macros.forEachTiddler.findTiddlers = function(whereClause, context, tiddlyWiki) {
	var result = [];
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(whereClause, context);
	tiddlyWiki.forEachTiddler(function(title,tiddler) {
		if (func(tiddler, context, undefined, undefined)) {
			result.push(tiddler);
		}
	});
	return result;
};

// Internal.
//
config.macros.forEachTiddler.createExtraParameterErrorElement = function(place, actionName, parameter, firstUnusedIndex) {
	var message = &quot;Extra parameter behind '&quot;+actionName+&quot;':&quot;;
	for (var i = firstUnusedIndex; i &lt; parameter.length; i++) {
		message += &quot; &quot;+parameter[i];
	}
	this.handleError(place, message);
};

// Internal.
//
config.macros.forEachTiddler.sortAscending = function(tiddlerA, tiddlerB) {
	var result = 
		(tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue) 
			? 0
			: (tiddlerA.forEachTiddlerSortValue &lt; tiddlerB.forEachTiddlerSortValue)
			   ? -1 
			   : +1; 
	return result;
};

// Internal.
//
config.macros.forEachTiddler.sortDescending = function(tiddlerA, tiddlerB) {
	var result = 
		(tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue) 
			? 0
			: (tiddlerA.forEachTiddlerSortValue &lt; tiddlerB.forEachTiddlerSortValue)
			   ? +1 
			   : -1; 
	return result;
};

// Internal.
//
config.macros.forEachTiddler.sortTiddlers = function(tiddlers, sortClause, ascending, context) {
	// To avoid evaluating the sortClause whenever two items are compared 
	// we pre-calculate the sortValue for every item in the array and store it in a 
	// temporary property (&quot;forEachTiddlerSortValue&quot;) of the tiddlers.
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(sortClause, context);
	var count = tiddlers.length;
	var i;
	for (i = 0; i &lt; count; i++) {
		var tiddler = tiddlers[i];
		tiddler.forEachTiddlerSortValue = func(tiddler,context, undefined, undefined);
	}

	// Do the sorting
	tiddlers.sort(ascending ? this.sortAscending : this.sortDescending);

	// Delete the temporary property that holds the sortValue.	
	for (i = 0; i &lt; tiddlers.length; i++) {
		delete tiddlers[i].forEachTiddlerSortValue;
	}
};


// Internal.
//
config.macros.forEachTiddler.trace = function(message) {
	displayMessage(message);
};

// Internal.
//
config.macros.forEachTiddler.traceMacroCall = function(place,macroName,params) {
	var message =&quot;&lt;&lt;&quot;+macroName;
	for (var i = 0; i &lt; params.length; i++) {
		message += &quot; &quot;+params[i];
	}
	message += &quot;&gt;&gt;&quot;;
	displayMessage(message);
};


// Internal.
//
// Creates an element that holds an error message
// 
config.macros.forEachTiddler.createErrorElement = function(place, exception) {
	var message = (exception.description) ? exception.description : exception.toString();
	return createTiddlyElement(place,&quot;span&quot;,null,&quot;forEachTiddlerError&quot;,&quot;&lt;&lt;forEachTiddler ...&gt;&gt;: &quot;+message);
};

// Internal.
//
// @param place [may be null]
//
config.macros.forEachTiddler.handleError = function(place, exception) {
	if (place) {
		this.createErrorElement(place, exception);
	} else {
		throw exception;
	}
};

// Internal.
//
// Encodes the given string.
//
// Replaces 
//	 &quot;$))&quot; to &quot;&gt;&gt;&quot;
//	 &quot;$)&quot; to &quot;&gt;&quot;
//
config.macros.forEachTiddler.paramEncode = function(s) {
	var reGTGT = new RegExp(&quot;\\$\\)\\)&quot;,&quot;mg&quot;);
	var reGT = new RegExp(&quot;\\$\\)&quot;,&quot;mg&quot;);
	return s.replace(reGTGT, &quot;&gt;&gt;&quot;).replace(reGT, &quot;&gt;&quot;);
};

// Internal.
//
// Returns the given original path (that is a file path, starting with &quot;file:&quot;)
// as a path to a local file, in the systems native file format.
//
// Location information in the originalPath (i.e. the &quot;#&quot; and stuff following)
// is stripped.
// 
config.macros.forEachTiddler.getLocalPath = function(originalPath) {
	// Remove any location part of the URL
	var hashPos = originalPath.indexOf(&quot;#&quot;);
	if(hashPos != -1)
		originalPath = originalPath.substr(0,hashPos);
	// Convert to a native file format assuming
	// &quot;file:///x:/path/path/path...&quot; - pc local file --&gt; &quot;x:\path\path\path...&quot;
	// &quot;file://///server/share/path/path/path...&quot; - FireFox pc network file --&gt; &quot;\\server\share\path\path\path...&quot;
	// &quot;file:///path/path/path...&quot; - mac/unix local file --&gt; &quot;/path/path/path...&quot;
	// &quot;file://server/share/path/path/path...&quot; - pc network file --&gt; &quot;\\server\share\path\path\path...&quot;
	var localPath;
	if(originalPath.charAt(9) == &quot;:&quot;) // pc local file
		localPath = unescape(originalPath.substr(8)).replace(new RegExp(&quot;/&quot;,&quot;g&quot;),&quot;\\&quot;);
	else if(originalPath.indexOf(&quot;file://///&quot;) === 0) // FireFox pc network file
		localPath = &quot;\\\\&quot; + unescape(originalPath.substr(10)).replace(new RegExp(&quot;/&quot;,&quot;g&quot;),&quot;\\&quot;);
	else if(originalPath.indexOf(&quot;file:///&quot;) === 0) // mac/unix local file
		localPath = unescape(originalPath.substr(7));
	else if(originalPath.indexOf(&quot;file:/&quot;) === 0) // mac/unix local file
		localPath = unescape(originalPath.substr(5));
	else // pc network file
		localPath = &quot;\\\\&quot; + unescape(originalPath.substr(7)).replace(new RegExp(&quot;/&quot;,&quot;g&quot;),&quot;\\&quot;);	
	return localPath;
};

// ---------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// ---------------------------------------------------------------------------
//
setStylesheet(
	&quot;.forEachTiddlerError{color: #ffffff;background-color: #880000;}&quot;,
	&quot;forEachTiddler&quot;);

//============================================================================
// End of forEachTiddler Macro
//============================================================================


//============================================================================
// String.startsWith Function
//============================================================================
//
// Returns true if the string starts with the given prefix, false otherwise.
//
version.extensions[&quot;String.startsWith&quot;] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: &quot;http://tiddlywiki.abego-software.de&quot;};
//
String.prototype.startsWith = function(prefix) {
	var n =  prefix.length;
	return (this.length &gt;= n) &amp;&amp; (this.slice(0, n) == prefix);
};



//============================================================================
// String.endsWith Function
//============================================================================
//
// Returns true if the string ends with the given suffix, false otherwise.
//
version.extensions[&quot;String.endsWith&quot;] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: &quot;http://tiddlywiki.abego-software.de&quot;};
//
String.prototype.endsWith = function(suffix) {
	var n = suffix.length;
	return (this.length &gt;= n) &amp;&amp; (this.right(n) == suffix);
};


//============================================================================
// String.contains Function
//============================================================================
//
// Returns true when the string contains the given substring, false otherwise.
//
version.extensions[&quot;String.contains&quot;] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: &quot;http://tiddlywiki.abego-software.de&quot;};
//
String.prototype.contains = function(substring) {
	return this.indexOf(substring) &gt;= 0;
};

//============================================================================
// Array.indexOf Function
//============================================================================
//
// Returns the index of the first occurance of the given item in the array or 
// -1 when no such item exists.
//
// @param item [may be null]
//
version.extensions[&quot;Array.indexOf&quot;] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: &quot;http://tiddlywiki.abego-software.de&quot;};
//
Array.prototype.indexOf = function(item) {
	for (var i = 0; i &lt; this.length; i++) {
		if (this[i] == item) {
			return i;
		}
	}
	return -1;
};

//============================================================================
// Array.contains Function
//============================================================================
//
// Returns true when the array contains the given item, otherwise false. 
//
// @param item [may be null]
//
version.extensions[&quot;Array.contains&quot;] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: &quot;http://tiddlywiki.abego-software.de&quot;};
//
Array.prototype.contains = function(item) {
	return (this.indexOf(item) &gt;= 0);
};

//============================================================================
// Array.containsAny Function
//============================================================================
//
// Returns true when the array contains at least one of the elements 
// of the item. Otherwise (or when items contains no elements) false is returned.
//
version.extensions[&quot;Array.containsAny&quot;] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: &quot;http://tiddlywiki.abego-software.de&quot;};
//
Array.prototype.containsAny = function(items) {
	for(var i = 0; i &lt; items.length; i++) {
		if (this.contains(items[i])) {
			return true;
		}
	}
	return false;
};


//============================================================================
// Array.containsAll Function
//============================================================================
//
// Returns true when the array contains all the items, otherwise false.
// 
// When items is null false is returned (even if the array contains a null).
//
// @param items [may be null] 
//
version.extensions[&quot;Array.containsAll&quot;] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: &quot;http://tiddlywiki.abego-software.de&quot;};
//
Array.prototype.containsAll = function(items) {
	for(var i = 0; i &lt; items.length; i++) {
		if (!this.contains(items[i])) {
			return false;
		}
	}
	return true;
};


} // of &quot;install only once&quot;

// Used Globals (for JSLint) ==============
// ... DOM
/*global 	document */
// ... TiddlyWiki Core
/*global 	convertUnicodeToUTF8, createTiddlyElement, createTiddlyLink, 
			displayMessage, endSaveArea, hasClass, loadFile, saveFile, 
			startSaveArea, store, wikify */
//}}}


/***
!Licence and Copyright
Copyright (c) abego Software ~GmbH, 2005 ([[www.abego-software.de|http://www.abego-software.de]])

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

Neither the name of abego Software nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/

</pre>
</div>
<div title="FormTiddlerPlugin" modifier="YBA" created="200809191401" modified="200809220801" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
&lt;&lt;checkForDataTiddlerPlugin&gt;&gt;
|''Name:''|FormTiddlerPlugin|
|''Version:''|1.0.6 (2007-06-24)|
|''Source:''|http://tiddlywiki.abego-software.de/#FormTiddlerPlugin|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''Licence:''|[[BSD open source license]]|
|''Macros:''|formTiddler, checkForDataTiddlerPlugin, newTiddlerWithForm|
|''Requires:''|DataTiddlerPlugin|
|''TiddlyWiki:''|1.2.38+, 2.0|
|''Browser:''|Firefox 1.0.4+; InternetExplorer 6.0|
!Description
Use form-based tiddlers to enter your tiddler data using text fields, listboxes, checkboxes etc. (All standard HTML Form input elements supported).

''Syntax:'' 
|&gt;|{{{&lt;&lt;}}}''formTiddler'' //tiddlerName//{{{&gt;&gt;}}}|
|//tiddlerName//|The name of the FormTemplate tiddler to be used to edit the data of the tiddler containing the macro.|

|&gt;|{{{&lt;&lt;}}}''newTiddlerWithForm'' //formTemplateName// //buttonLabel// [//titleExpression// [''askUser'']] {{{&gt;&gt;}}}|
|//formTemplateName//|The name of the tiddler that defines the form the new tiddler should use.|
|//buttonLabel//|The label of the button|
|//titleExpression//|A (quoted) JavaScript String expression that defines the title (/name) of the new tiddler.|
|''askUser''|Typically the user is not asked for the title when a title is specified (and not yet used). When ''askUser'' is given the user will be asked in any case. This may be used when the calculated title is just a suggestion that must be confirmed by the user|
|&gt;|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|

For details and how to use the macros see the [[introduction|FormTiddler Introduction]] and the [[examples|FormTiddler Examples]].

!Revision history
* v1.0.6 (2007-06-24)
** Fixed problem when using SELECT component in Internet Explorer (thanks to MaikBoenig for reporting)
* v1.0.5 (2006-02-24)
** Removed &quot;debugger;&quot; instruction
* v1.0.4 (2006-02-07)
** Bug: On IE no data is written to data section when field values changed (thanks to KenGirard for reporting)
* v1.0.3 (2006-02-05)
** Bug: {{{&quot;No form template specified in &lt;&lt;formTiddler&gt;&gt;&quot;}}} when using formTiddler macro on InternetExplorer (thanks to KenGirard for reporting)
* v1.0.2 (2006-01-06)
** Support TiddlyWiki 2.0
* v1.0.1 (2005-12-22)
** Features: 
*** Support InternetExplorer
*** Added newTiddlerWithForm Macro
* v1.0.0 (2005-12-14)
** initial version

!Code
***/
//{{{

//============================================================================
//============================================================================
//						FormTiddlerPlugin
//============================================================================
//============================================================================

if (!window.abego) window.abego = {};

abego.getOptionsValue = function(element,i) {
	var v = element.options[i].value;
	if (!v &amp;&amp; element.options[i].text)
		v = element.options[i].text;
	return v;
};

version.extensions.FormTiddlerPlugin = {
	major: 1, minor: 0, revision: 5,
	date: new Date(2006, 2, 24), 
	type: 'plugin',
	source: &quot;http://tiddlywiki.abego-software.de/#FormTiddlerPlugin&quot;
};

// For backward compatibility with v1.2.x
//
if (!window.story) window.story=window; 
if (!TiddlyWiki.prototype.getTiddler) TiddlyWiki.prototype.getTiddler = function(title) { return t = this.tiddlers[title]; return (t != undefined &amp;&amp; t instanceof Tiddler) ? t : null; } 

//============================================================================
// formTiddler Macro
//============================================================================

// -------------------------------------------------------------------------------
// Configurations and constants 
// -------------------------------------------------------------------------------

config.macros.formTiddler = {
	// Standard Properties
	label: &quot;formTiddler&quot;,
	version: {major: 1, minor: 0, revision: 4, date: new Date(2006, 2, 7)},
	prompt: &quot;Edit tiddler data using forms&quot;,

	// Define the &quot;setters&quot; that set the values of INPUT elements of a given type
	// (must match the corresponding &quot;getter&quot;)
	setter: {  
		button:				function(e, value) {/*contains no data */ },
		checkbox:			function(e, value) {e.checked = value;},
		file:				function(e, value) {try {e.value = value;} catch(e) {/* ignore, possibly security error*/}},
		hidden:				function(e, value) {e.value = value;},
		password:			function(e, value) {e.value = value;},
		radio:				function(e, value) {e.checked = (e.value == value);},
		reset:				function(e, value) {/*contains no data */ },
		&quot;select-one&quot;:		function(e, value) {config.macros.formTiddler.setSelectOneValue(e,value);},
		&quot;select-multiple&quot;:	function(e, value) {config.macros.formTiddler.setSelectMultipleValue(e,value);},
		submit:				function(e, value) {/*contains no data */},
		text:				function(e, value) {e.value = value;},
		textarea:			function(e, value) {e.value = value;}
	},

	// Define the &quot;getters&quot; that return the value of INPUT elements of a given type
	// Return undefined to not store any data.
	getter: {  
		button:				function(e, value) {return undefined;},
		checkbox:			function(e, value) {return e.checked;},
		file:				function(e, value) {return e.value;},
		hidden:				function(e, value) {return e.value;},
		password:			function(e, value) {return e.value;},
		radio:				function(e, value) {return e.checked ? e.value : undefined;},
		reset:				function(e, value) {return undefined;},
		&quot;select-one&quot;:		function(e, value) {return config.macros.formTiddler.getSelectOneValue(e);},
		&quot;select-multiple&quot;:	function(e, value) {return config.macros.formTiddler.getSelectMultipleValue(e);},
		submit:				function(e, value) {return undefined;},
		text:				function(e, value) {return e.value;},
		textarea:			function(e, value) {return e.value;}
	}
};


// -------------------------------------------------------------------------------
// The formTiddler Macro Handler 
// -------------------------------------------------------------------------------

config.macros.formTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	if (!config.macros.formTiddler.checkForExtensions(place, macroName)) {
		return;
	}
	// --- Parsing ------------------------------------------

	var i = 0; // index running over the params

	// get the name of the form template tiddler
	var formTemplateName = undefined;
	if (i &lt; params.length) {
		formTemplateName = params[i];
		i++;
	}

	if (!formTemplateName) {
		config.macros.formTiddler.createErrorElement(place, &quot;No form template specified in &lt;&lt;&quot; + macroName + &quot;&gt;&gt;.&quot;);
		return;
	}


	// --- Processing ------------------------------------------

	// Get the form template text. 
	// (This contains the INPUT elements for the form.)
	var formTemplateTiddler = store.getTiddler(formTemplateName);
	if (!formTemplateTiddler) {
		config.macros.formTiddler.createErrorElement(place, &quot;Form template '&quot; + formTemplateName + &quot;' not found.&quot;);
		return;
	}
	var templateText = formTemplateTiddler.text;
	if(!templateText) {
		// Shortcut: when template text is empty we do nothing.
		return;
	}

	// Get the name of the tiddler containing this &quot;formTiddler&quot; macro
	// (i.e. the tiddler, that will be edited and that contains the data)
	var tiddlerName = config.macros.formTiddler.getContainingTiddlerName(place);

	// Append a &quot;form&quot; element. 
	var formName = &quot;form&quot;+formTemplateName+&quot;__&quot;+tiddlerName;
	var e = document.createElement(&quot;form&quot;);
	e.setAttribute(&quot;name&quot;, formName);
	place.appendChild(e);

	// &quot;Embed&quot; the elements defined by the templateText (i.e. the INPUT elements) 
	// into the &quot;form&quot; element we just created
	wikify(templateText, e);

	// Initialize the INPUT elements.
	config.macros.formTiddler.initValuesAndHandlersInFormElements(formName, DataTiddler.getDataObject(tiddlerName));
}


// -------------------------------------------------------------------------------
// Form Data Access 
// -------------------------------------------------------------------------------

// Internal.
//
// Initialize the INPUT elements of the form with the values of their &quot;matching&quot;
// data fields in the tiddler. Also setup the onChange handler to ensure that
// changes in the INPUT elements are stored in the tiddler's data.
//
config.macros.formTiddler.initValuesAndHandlersInFormElements = function(formName, data) {
	// config.macros.formTiddler.trace(&quot;initValuesAndHandlersInFormElements(formName=&quot;+formName+&quot;, data=&quot;+data+&quot;)&quot;);

	// find the form
	var form = config.macros.formTiddler.findForm(formName);
	if (!form) {
		return;
	}

	try {
		var elems = form.elements;
		for (var i = 0; i &lt; elems.length; i++) {
			var c = elems[i];
		
			var setter = config.macros.formTiddler.setter[c.type];
			if (setter) {
				var value = data[c.name];
				if (value != null) {
					setter(c, value);
				}
				c.onchange = onFormTiddlerChange;
			} else {
				config.macros.formTiddler.displayFormTiddlerError(&quot;No setter defined for INPUT element of type '&quot;+c.type+&quot;'. (Element '&quot;+c.name+&quot;' in form '&quot;+formName+&quot;')&quot;);
			}
		}
	} catch(e) {
		config.macros.formTiddler.displayFormTiddlerError(&quot;Error when updating elements with new formData. &quot;+e);
	}
}


// Internal.
//
// @return [may be null]
//
config.macros.formTiddler.findForm = function(formName) {
	// We must manually iterate through the document's forms, since
	// IE does not support the &quot;document[formName]&quot; approach

	var forms = window.document.forms;
	for (var i = 0; i &lt; forms.length; i++) {
		var form = forms[i];
		if (form.name == formName) {
			return form;
		}
	}

	return null;
}


// Internal.
//
config.macros.formTiddler.setSelectOneValue = function(element,value) {
	var n = element.options.length;
	for (var i = 0; i &lt; n; i++) {
		element.options[i].selected = abego.getOptionsValue(element,i) == value;
	}
}

// Internal.
//
config.macros.formTiddler.setSelectMultipleValue = function(element,value) {
	var values = {};
	for (var i = 0; i &lt; value.length; i++) {
		values[value[i]] = true;
	}
	
	var n = element.length;
	for (var i = 0; i &lt; n; i++) {
		element.options[i].selected = !(!values[abego.getOptionsValue(element,i)]);
	}
}

// Internal.
//
config.macros.formTiddler.getSelectOneValue = function(element) {
	var i = element.selectedIndex;
	return (i &gt;= 0) ? abego.getOptionsValue(element,i) : null;
}

// Internal.
//
config.macros.formTiddler.getSelectMultipleValue = function(element) {
	var values = [];
	var n = element.length;
	for (var i = 0; i &lt; n; i++) {
		if (element.options[i].selected) {
			values.push(abego.getOptionsValue(element,i));
		}
	}
	return values;
}



// -------------------------------------------------------------------------------
// Helpers 
// -------------------------------------------------------------------------------

// Internal.
//
config.macros.formTiddler.checkForExtensions = function(place,macroName) {
	if (!version.extensions.DataTiddlerPlugin) {
		config.macros.formTiddler.createErrorElement(place, &quot;&lt;&lt;&quot; + macroName + &quot;&gt;&gt; requires the DataTiddlerPlugin. (You can get it from http://tiddlywiki.abego-software.de/#DataTiddlerPlugin)&quot;);
		return false;
	}
	return true;
}

// Internal.
//
// Displays a trace message in the &quot;TiddlyWiki&quot; message pane.
// (used for debugging)
//
config.macros.formTiddler.trace = function(s) {
	displayMessage(&quot;Trace: &quot;+s);
}

// Internal.
//
// Display some error message in the &quot;TiddlyWiki&quot; message pane.
//
config.macros.formTiddler.displayFormTiddlerError = function(s) {
	alert(&quot;FormTiddlerPlugin Error: &quot;+s);
}

// Internal.
//
// Creates an element that holds an error message
// 
config.macros.formTiddler.createErrorElement = function(place, message) {
	return createTiddlyElement(place,&quot;span&quot;,null,&quot;formTiddlerError&quot;,message);
}

// Internal.
//
// Returns the name of the tiddler containing the given element.
// 
config.macros.formTiddler.getContainingTiddlerName = function(element) {
	return story.findContainingTiddler(element).id.substr(7);
}

// -------------------------------------------------------------------------------
// Event Handlers 
// -------------------------------------------------------------------------------

// This function must be called by the INPUT elements whenever their
// data changes. Typically this is done through an &quot;onChange&quot; handler.
//
function onFormTiddlerChange (e) {
	// config.macros.formTiddler.trace(&quot;onFormTiddlerChange &quot;+e);

	if (!e) var e = window.event;

	var target = resolveTarget(e);
	var tiddlerName = config.macros.formTiddler.getContainingTiddlerName(target);
	var getter = config.macros.formTiddler.getter[target.type];
	if (getter) {
		var value = getter(target);
		DataTiddler.setData(tiddlerName, target.name, value);
	} else {
		config.macros.formTiddler.displayFormTiddlerError(&quot;No getter defined for INPUT element of type '&quot;+target.type+&quot;'. (Element '&quot;+target.name+&quot;' used in tiddler '&quot;+tiddlerName+&quot;')&quot;);
	}
}

// ensure that the function can be used in HTML event handler
window.onFormTiddlerChange = onFormTiddlerChange;


// -------------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// -------------------------------------------------------------------------------

setStylesheet(
	&quot;.formTiddlerError{color: #ffffff;background-color: #880000;}&quot;,
	&quot;formTiddler&quot;);


//============================================================================
// checkForDataTiddlerPlugin Macro
//============================================================================

config.macros.checkForDataTiddlerPlugin = {
	// Standard Properties
	label: &quot;checkForDataTiddlerPlugin&quot;,
	version: {major: 1, minor: 0, revision: 0, date: new Date(2005, 12, 14)},
	prompt: &quot;Check if the DataTiddlerPlugin exists&quot;
}

config.macros.checkForDataTiddlerPlugin.handler = function(place,macroName,params) {
	config.macros.formTiddler.checkForExtensions(place, config.macros.formTiddler.label);
}



//============================================================================
// newTiddlerWithForm Macro
//============================================================================

config.macros.newTiddlerWithForm = {
	// Standard Properties
	label: &quot;newTiddlerWithForm&quot;,
	version: {major: 1, minor: 0, revision: 1, date: new Date(2006, 1, 6)},
	prompt: &quot;Creates a new Tiddler with a &lt;&lt;formTiddler ...&gt;&gt; macro&quot;
}

config.macros.newTiddlerWithForm.handler = function(place,macroName,params) {
	// --- Parsing ------------------------------------------

	var i = 0; // index running over the params

	// get the name of the form template tiddler
	var formTemplateName = undefined;
	if (i &lt; params.length) {
		formTemplateName = params[i];
		i++;
	}

	if (!formTemplateName) {
		config.macros.formTiddler.createErrorElement(place, &quot;No form template specified in &lt;&lt;&quot; + macroName + &quot;&gt;&gt;.&quot;);
		return;
	}

	// get the button label
	var buttonLabel = undefined;
	if (i &lt; params.length) {
		buttonLabel = params[i];
		i++;
	}

	if (!buttonLabel) {
		config.macros.formTiddler.createErrorElement(place, &quot;No button label specified in &lt;&lt;&quot; + macroName + &quot;&gt;&gt;.&quot;);
		return;
	}

	// get the (optional) tiddlerName script and &quot;askUser&quot;
	var tiddlerNameScript = undefined;
	var askUser = false;
	if (i &lt; params.length) {
		tiddlerNameScript = params[i];
		i++;

		if (i &lt; params.length &amp;&amp; params[i] == &quot;askUser&quot;) {
			askUser = true;
			i++;
		}
	}

	// --- Processing ------------------------------------------

	if(!readOnly) {
		var onClick = function() {
			var tiddlerName;
			if (tiddlerNameScript) {
				try {
					tiddlerName = eval(tiddlerNameScript);
				} catch (ex) {
				}
			}
			if (!tiddlerName || askUser) {
				tiddlerName = prompt(&quot;Please specify a tiddler name.&quot;, askUser ? tiddlerName : &quot;&quot;);
			}
			while (tiddlerName &amp;&amp; store.getTiddler(tiddlerName)) {
				tiddlerName = prompt(&quot;A tiddler named '&quot;+tiddlerName+&quot;' already exists.\n\n&quot;+&quot;Please specify a tiddler name.&quot;, tiddlerName);
			}

			// tiddlerName is either null (user canceled) or a name that is not yet in the store.
			if (tiddlerName) {
				var body = &quot;&lt;&lt;formTiddler [[&quot;+formTemplateName+&quot;]]&gt;&gt;&quot;;
				var tags = [];
				store.saveTiddler(tiddlerName,tiddlerName,body,config.options.txtUserName,new Date(),tags);
				story.displayTiddler(null,tiddlerName,1);
			}
		}

		createTiddlyButton(place,buttonLabel,buttonLabel,onClick);
    }
}

//}}}


/***
!Licence and Copyright
Copyright (c) abego Software ~GmbH, 2005 ([[www.abego-software.de|http://www.abego-software.de]])

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

Neither the name of abego Software nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/
</pre>
</div>
<div title="HideWhenPlugin" modifier="YBA" created="200809101224" modified="200809220801" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name:|HideWhenPlugin|
|Description:|Allows conditional inclusion/exclusion in templates|
|Version:|3.1 ($Rev: 3919 $)|
|Date:|$Date: 2008-03-13 02:03:12 +1000 (Thu, 13 Mar 2008) $|
|Source:|http://mptw.tiddlyspot.com/#HideWhenPlugin|
|Author:|Simon Baird &lt;simon.baird@gmail.com&gt;|
|License:|http://mptw.tiddlyspot.com/#TheBSDLicense|
For use in ViewTemplate and EditTemplate. Example usage:
{{{&lt;div macro=&quot;showWhenTagged Task&quot;&gt;[[TaskToolbar]]&lt;/div&gt;}}}
{{{&lt;div macro=&quot;showWhen tiddler.modifier == 'BartSimpson'&quot;&gt;&lt;img src=&quot;bart.gif&quot;/&gt;&lt;/div&gt;}}}
***/
//{{{

window.hideWhenLastTest = false;

window.removeElementWhen = function(test,place) {
	window.hideWhenLastTest = test;
	if (test) {
		removeChildren(place);
		place.parentNode.removeChild(place);
	}
};


merge(config.macros,{

	hideWhen: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( eval(paramString), place);
	}},

	showWhen: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !eval(paramString), place);
	}},

	hideWhenTagged: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.tags.containsAll(params), place);
	}},

	showWhenTagged: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !tiddler.tags.containsAll(params), place);
	}},

	hideWhenTaggedAny: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.tags.containsAny(params), place);
	}},

	showWhenTaggedAny: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !tiddler.tags.containsAny(params), place);
	}},

	hideWhenTaggedAll: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.tags.containsAll(params), place);
	}},

	showWhenTaggedAll: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !tiddler.tags.containsAll(params), place);
	}},

	hideWhenExists: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( store.tiddlerExists(params[0]) || store.isShadowTiddler(params[0]), place);
	}},

	showWhenExists: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !(store.tiddlerExists(params[0]) || store.isShadowTiddler(params[0])), place);
	}},

	hideWhenTitleIs: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.title == params[0], place);
	}},

	showWhenTitleIs: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.title != params[0], place);
	}},

	'else': { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !window.hideWhenLastTest, place);
	}}

});

//}}}

</pre>
</div>
<div title="HistoGramMacro" modifier="YBA" created="200809111234" modified="200809221224" tags="systemConfig excludeSearch excludeLists IDEIA" changecount="15">
<pre>/***
|Name|HistoGramMacro|
|Source||
|Documentation||
|Version|1.0.3|
|Author|Eric Shulman - ELS Design Studios &amp; Jean-Cédric THÉROND - www.IDEIA.fr|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1.0|
|Type|plugin|
|Requires||
|Overrides||
|Description|Generate an histogram from datas in tiddlers|
It's a new usage of the SliceGridPlugin core and my new Sparkline to generate histogram.
It make the sum of each value in each columns of a slicegrid and make an histogram with.
!!!!!Documentation
//{{{
&lt;&lt;histoGram tags:task width:200 slices:+SprintDays&gt;&gt;
&lt;&lt;sliceGrid edit inline tags:={{tiddler.title}} slices:+SprintDays&gt;&gt;
&lt;&lt;histoGram tags:={{tiddler.title}} width:200 slices:+SprintDays&gt;&gt;

//}}}
!!!!!Revisions
&lt;&lt;&lt;
2008.09.11 [1.0] Modifications by J.-C. T.
2008.08.15 [1.0.3] add brackets around row headings when wikifying so that non-wikiword tiddler titles are linked
2008.08.13 [1.0.2] wikify row headings (in addition to slice value cells) whenever 'wikify' param is used
|please see [[SliceGridPluginInfo]] for additional revision details|
2007.01.30 [0.0.1] started
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.histoGram= {major: 1, minor: 0, revision: 0, date: new Date(2008,9,11)};

config.macros.histoGram= {
	sizeSliceName: &quot;TiddlerSize&quot;, // shadow slice used for retrieving and showing tiddler size as a grid column
	noSlicesMsg: &quot;\n|~SliceGrid: there are no slices to display|\n&quot;,
	handler:
	function(place,macroName,params) {
		this.slicesRE=/(?:^\|\s*[\'\/]*~?(\w+)\:?[\'\/]*\s*\|\s*(.*?)\s*\|$)/gm;
		this.wikify=false; // wiki-syntax in slices
		this.edit=false; // 'click-to-edit' feature
		this.verbose=false; // debugging/performance feedback messages
		this.alltiddlers=false; // rows for all tiddlers (even if no slices)
		this.inline=false; // slice value in cell (instead of mouseover)
		this.headers=false; // column headers
		this.cliplength=0; // limit inline display text length (0=no limit)
		this.slices=[]; // slices to show (columns)
		var tablewidth=&quot;100&quot;;
		var tableheight=&quot;100&quot;;
		var p=params.shift();


		var rowSum =[];


		while (p) {
			if (p.substr(0,6).toUpperCase()==&quot;WIDTH:&quot;)
				tablewidth=p.substr(6);
			else if (p.substr(0,7).toUpperCase()==&quot;HEIGHT:&quot;)
				tableheight=p.substr(7);
			else if (p.substr(0,5).toUpperCase()==&quot;TAGS:&quot;) {
				if (p.substr(5,1)==&quot;@&quot;) { // get tags from tiddler
					var tid=store.getTiddler(p.substr(6));
					if (p.substr(6)==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tid=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tid) var tags=tid.tags;
				}
				else if (p.substr(5,1)==&quot;+&quot;) { // get tags from tiddler contents
					var tid=store.getTiddler(p.substr(6));
					if (p.substr(6)==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tid=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tid)
						{ var tags=tid.text.readBracketedList(); }
				}
                                else if (p.substr(5,1)==&quot;=&quot;) { // get tags from value
                                       var tags=p.substr(6).readMacroParams();
                                }
				else var tags=p.substr(5).readBracketedList();
			}
			else if (p.substr(0,7).toUpperCase()==&quot;SLICES:&quot;) {
				if (p.substr(7,1)==&quot;@&quot;) { // get slices from tiddler
					var tid=p.substr(8);
					var tiddler=store.getTiddler(tid);
					if (tid==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tiddler=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tiddler) {
						var slices=tiddler.getSlices(tiddler.title); for (var s in slices) this.slices.push(s);
					}
				}
				else if (p.substr(7,1)==&quot;+&quot;) { // get slices from tiddler contents
					var tid=p.substr(8);
					var tiddler=store.getTiddler(tid);
					if (tid==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tiddler=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tiddler)
						{ this.slices=tiddler.text.readBracketedList(); }
				}
				else this.slices=p.substr(7).readBracketedList();
			}
			else {
				if (!isNaN(p.toUpperCase())) { this.cliplength=p; break; }
				else displayMessage(&quot;unrecognized parameter: &quot;+p);
			}
			p=params.shift();
		}
		// get rows and columns
		var rows=[]; var cols=[]; var total=0;
		var tiddlers=store.getTiddlers(&quot;title&quot;,&quot;excludeLists&quot;); // get all tiddlers (except hidden ones)
		if (tags &amp;&amp; tags.length) for (t=0;t&lt;tiddlers.length;t++) // filter out tiddlers with no matching tags
			if (!tiddlers[t].tags.containsAll(tags)) { tiddlers.splice(t,1); t--; } // remove non-matching tiddler -- YBA 22.09.08 patch to have an AND operation
		for (i=0; i&lt;tiddlers.length; i++) {
			var slices=this.getSlices(tiddlers[i].title);
			var count=0; for (var s in slices) { cols.pushUnique(s); count++; total++;}
			if (count || this.alltiddlers) rows.push(tiddlers[i].title); // only show rows for tiddlers with slices
		}
		if (!cols.length) { wikify(this.noSlicesMsg,place); return; }
		if (this.verbose) displayMessage(&quot;Found %0 slices using %1 slice names in %2 tiddlers&quot;.format([total, cols.length,rows.length]));

		// get optional list of slices to show
		if (this.slices.length) var cols=this.slices;

		// generate HTML table



for (var i=0;i&lt;cols.length;i++){
var sumTemp = 0;

	for (var j=0;j&lt;rows.length;j++) {
		sumTemp += parseInt( (isNaN(this.getSlices(rows[j])[cols[i]])) ? 0 : this.getSlices(rows[j])[cols[i]] );
	}

	rowSum[i] = sumTemp; 
}



	var rowSumlength = rowSum.length;
	var highest = [];
	var boxWidth = tablewidth;
	var boxHeight = tableheight;
	var tickWidth = 0;
	var tickBorderWidth = 0;
	var v;

	if(rowSumlength &lt; 1){
		return;
	}else{
		for (var i = 0, item; item = rowSum[i]; i++ ){
			highest[i] = item;
		}
		highest.sort(config.macros.histoGram.byNum);
	}
	if (highest[0] &gt; 0){
	tickWidth = parseInt(boxWidth/rowSumlength);
	var box = createTiddlyElement(place,&quot;span&quot;,null,&quot;sparkline&quot;,'');
	box.title = rowSum.join(&quot;,&quot;);
	box.style.display = 'block';
	box.style.width = rowSumlength * tickWidth+&quot;px&quot;;
	box.style.height = boxHeight+&quot;px&quot;;
	box.style.position = &quot;relative&quot;;

	for( var i = 0 ; i &lt; rowSumlength ; i++ ) {
		var tick = document.createElement(&quot;img&quot;);
		tick.style.border = tickBorderWidth;
		tick.className = &quot;sparktick&quot;;
		tick.src = &quot;data:image/gif,GIF89a%01%00%01%00%91%FF%00%FF%FF%FF%00%00%00%C0%C0%C0%00%00%00!%F9%04%01%00%00%02%00%2C%00%00%00%00%01%00%01%00%40%02%02T%01%00%3B&quot;;

		tick.style.width = tickWidth+'px';
		tick.style.height = (isNaN((rowSum[i]/highest[0])*boxHeight)?boxHeight:parseInt((rowSum[i]/highest[0])*boxHeight)) +&quot;px&quot;;
		tick.style.float = 'left';
		box.appendChild(tick);
	}
	}else{
		displayMessage(&quot;no data&quot;);
	}

	},
	toggleAllColumns:
	function(here,event,defOpen) {
		if (here.expanded==undefined) here.expanded=defOpen;
		var ex=here.expanded=!here.expanded; 
		here.innerHTML=ex?&quot;&amp;lt;&amp;lt;&amp;lt;&quot;:&quot;&amp;gt;&amp;gt;&amp;gt;&quot;;
		here.title=ex?'hide all column headings':'show all column headings';
		var cells=here.parentNode.parentNode.getElementsByTagName(&quot;td&quot;);
		for (i=1; i&lt;cells.length; i++) cells[i].firstChild.style.display=ex?&quot;inline&quot;:&quot;none&quot;;
		event.cancelBubble = true;
		if (event.stopPropagation) event.stopPropagation();
		return(false);
	},
	toggleColumn:
	function(here,event) {
		here.firstChild.style.display=(here.firstChild.style.display==&quot;none&quot;)?&quot;inline&quot;:&quot;none&quot;;
		event.cancelBubble = true;
		if (event.stopPropagation) event.stopPropagation();
		return(false);
	},
	editSlice:
	function(here,event,tid,slice,inline) {
		// replace prompt box with edit field that is displayed until onBlur
		if (here.editing) return false; // already editing... don't re-init

		var onkeyup='if (event.keyCode==13)'; // SAVE and END
		onkeyup+='	{ config.macros.sliceGrid.setSlice(this.getAttribute(\&quot;tid\&quot;),&quot;'+slice+'&quot;,this.value); this.blur(); }';
		onkeyup+='else if (event.keyCode==27)'; // END without saving
		onkeyup+='	{ this.blur(); }';

		var onblur=&quot;var tid=this.getAttribute(\&quot;tid\&quot;); var slice=\&quot;&quot;+slice+&quot;\&quot;; var currval=config.macros.sliceGrid.getSlice(tid,slice); &quot;;
		onblur+=&quot;if (this.value!=currval &amp;&amp; confirm(tid+\&quot;::\&quot;+slice+\&quot; has changed... save value?\&quot;))&quot;;
		onblur+=&quot;	{ config.macros.sliceGrid.setSlice(tid,slice,this.value); currval=this.value; }&quot;;
		onblur+=&quot;var target=this.parentNode;&quot;;
		onblur+=&quot;target.editing=false;&quot;;
		onblur+=&quot;target.style.backgroundColor=target.getAttribute(\&quot;savedColor\&quot;);&quot;;
		onblur+=&quot;target.style.padding=target.getAttribute(\&quot;savedPadding\&quot;);&quot;;
		onblur+=&quot;target.style.width=target.getAttribute(\&quot;savedWidth\&quot;);&quot;;
		onblur+=&quot;if (config.macros.sliceGrid.wikify) { removeChildren(target); wikify(currval,target); }&quot;;
		onblur+=&quot;else target.innerHTML=&quot;+(inline?'currval':'\&quot;&amp;nbsp;\&quot;')+&quot;;&quot;;

		var style=&quot;font-size:1em;font-family:inherit;width:95%;margin:0px;padding:1px 0px 1px 3px;border:0;&quot;;
		var title=tid+&quot;::&quot;+slice+&quot; (ENTER=submit, ESC=cancel)&quot;;

		here.setAttribute(&quot;savedWidth&quot;,here.style.width); if (!inline) here.style.width=&quot;99%&quot;;
		here.setAttribute(&quot;savedPadding&quot;,here.style.padding); here.style.padding=&quot;0px&quot;; 
		here.setAttribute(&quot;savedColor&quot;,here.style.backgroundColor); here.style.backgroundColor=&quot;#fff&quot;;
		here.innerHTML=&quot;&lt;input type='text' tid=\&quot;&quot;+tid.replace(/&quot;/g,&quot;&amp;#x22;&quot;)+&quot;\&quot; onkeyup='&quot;+onkeyup+&quot;' onblur='&quot;+onblur+&quot;' style='&quot;+style+&quot;' title='&quot;+title+&quot;'&gt;&quot;;
		here.firstChild.value=config.macros.sliceGrid.getSlice(tid,slice); // avoids conflicts with nested quoting in HTML
		here.firstChild.focus();
		here.firstChild.select();
		here.editing=true;

		event.cancelBubble = true; if (event.stopPropagation) event.stopPropagation(); return(false);
	},
	getSlice:
	function(tid,slice) {
		var slices=config.macros.sliceGrid.getSlices(tid);
		return slices[slice]?slices[slice]:&quot;&quot;;
	},
	getSlices:
	function(tid) {
		var slices = {};
		var text = store.getTiddlerText(tid,&quot;&quot;);
		slices[this.sizeSliceName]=text.length.toString();  // 'shadow slice' for displaying tiddler size
		this.slicesRE.lastIndex = 0;
		do {
			var m = this.slicesRE.exec(text);
			if (m) { if (m[1]) slices[m[1]] = m[2]; else slices[m[3]] = m[4]; }
		} while(m);
		return slices;
	},
	setSlice:
	function(tid,slice,val) {
		var oldval=this.getSlice(tid,slice);
		if (val==oldval) return false; // value is unchanged... do nothing
		// find slice within tiddler text
		var tiddler=store.getTiddler(tid);
		this.slicesRE.lastIndex = 0;
		var lastIndex=0;
		do {
			var m=this.slicesRE.exec(tiddler.text);
			if (m &amp;&amp; (m[1]==slice||m[3]==slice)) break;
			if (m) lastIndex=this.slicesRE.lastIndex;
		} while(m);
		if (m) { // if matching slice was found
			if (oldval.length) { // replace old value with new value
				var pos=RegExp.lastMatch.indexOf(oldval);
				var newSlice=RegExp.lastMatch.substr(0,pos)+val+RegExp.lastMatch.substr(pos+oldval.length);
			} else // insert new value into empty slice
				var newSlice=RegExp.lastMatch.substr(0,RegExp.lastMatch.length-1)+val+&quot;|&quot;;
			var newText=RegExp.leftContext+newSlice+RegExp.rightContext;
		} else { // create new slice following last slice (or at start of tiddler if no slices)
			var newSlice=&quot;|&quot;+slice+&quot;|&quot;+val+&quot;|\n&quot;;
			var newText=newSlice+tiddler.text
			if (lastIndex) var newText=tiddler.text.substr(0,lastIndex+1)+newSlice+tiddler.text.substr(lastIndex+1);
		}
		// write tiddler contents
		var user=config.options.txtUserName; var mod=new Date();
		if (config.options.chkForceMinorUpdate)
			{ var user=tiddler.modifier; var mod=tiddler.modified; }
		store.saveTiddler(tid,tid,newText,user,mod,tiddler.tags,tiddler.fields);
		displayMessage(tid+&quot;::&quot;+slice+&quot; has been &quot;+(m?&quot;updated&quot;:&quot;created&quot;));
		return(false);
	}
};

config.macros.histoGram.byNum = function(a,b)
{
return parseInt(b)-parseInt(a); 
}
//}}}</pre>
</div>
<div title="HomeParameters" modifier="YBA" created="200809160849" changecount="1" creator="YBA">
<pre>|UploadUserName:|YBA|
|UploadStoreUrl:|http://192.168.2.23/scrum/store.php|
|UploadDir:|.|
|UploadFilename:|scrum.html|
|UploadBackupDir:||</pre>
</div>
<div title="InProgress" modifier="YBA" created="200809151416" tags="status" changecount="1">
<pre></pre>
</div>
<div title="InlineJavascriptPlugin" modifier="YBA" created="200809091159" modified="200809220809" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|InlineJavascriptPlugin|
|Source|http://www.TiddlyTools.com/#InlineJavascriptPlugin|
|Documentation|http://www.TiddlyTools.com/#InlineJavascriptPluginInfo|
|Version|1.9.3|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Description|Insert Javascript executable code directly into your tiddler content.|
''Call directly into TW core utility routines, define new functions, calculate values, add dynamically-generated TiddlyWiki-formatted output'' into tiddler content, or perform any other programmatic actions each time the tiddler is rendered.
!!!!!Documentation
&gt;see [[InlineJavascriptPluginInfo]]
!!!!!Revisions
&lt;&lt;&lt;
2008.06.11 [1.9.3] added $(...) function as 'shorthand' convenience syntax for document.getElementById()
2008.03.03 [1.9.2] corrected declaration of wikifyPlainText() for 'TW 2.1.x compatibility fallback' (fixes Safari &quot;parse error&quot;)
2008.02.23 [1.9.1] in onclick function, use string instead of array for 'bufferedHTML' attribute on link element (fixes IE errors)
2008.02.21 [1.9.0] 'onclick' scripts now allow returned text (or document.write() calls) to be wikified into a span that immediately follows the onclick link.  Also, added default 'return false' handling if no return value provided (prevents HREF from being triggered -- return TRUE to allow HREF to be processed).  Thanks to Xavier Verges for suggestion and preliminary code.
|please see [[InlineJavascriptPluginInfo]] for additional revision details|
2005.11.08 [1.0.0] initial release
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.inlineJavascript= {major: 1, minor: 9, revision: 3, date: new Date(2008,6,11)};

config.formatters.push( {
	name: &quot;inlineJavascript&quot;,
	match: &quot;\\&lt;script&quot;,
	lookahead: &quot;\\&lt;script(?: src=\\\&quot;((?:.|\\n)*?)\\\&quot;)?(?: label=\\\&quot;((?:.|\\n)*?)\\\&quot;)?(?: title=\\\&quot;((?:.|\\n)*?)\\\&quot;)?(?: key=\\\&quot;((?:.|\\n)*?)\\\&quot;)?( show)?\\&gt;((?:.|\\n)*?)\\&lt;/script\\&gt;&quot;,

	handler: function(w) {
		var lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
		lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = lookaheadRegExp.exec(w.source)
		if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart) {
			var src=lookaheadMatch[1];
			var label=lookaheadMatch[2];
			var tip=lookaheadMatch[3];
			var key=lookaheadMatch[4];
			var show=lookaheadMatch[5];
			var code=lookaheadMatch[6];
			if (src) { // load a script library
				// make script tag, set src, add to body to execute, then remove for cleanup
				var script = document.createElement(&quot;script&quot;); script.src = src;
				document.body.appendChild(script); document.body.removeChild(script);
			}
			if (code) { // there is script code
				if (show) // show inline script code in tiddler output
					wikify(&quot;{{{\n&quot;+lookaheadMatch[0]+&quot;\n}}}\n&quot;,w.output);
				if (label) { // create a link to an 'onclick' script
					// add a link, define click handler, save code in link (pass 'place'), set link attributes
					var link=createTiddlyElement(w.output,&quot;a&quot;,null,&quot;tiddlyLinkExisting&quot;,wikifyPlainText(label));
					var fixup=code.replace(/document.write\s*\(/gi,'place.bufferedHTML+=(');
					link.code=&quot;function _out(place){&quot;+fixup+&quot;\n};_out(this);&quot;
					link.tiddler=w.tiddler;
					link.onclick=function(){
						this.bufferedHTML=&quot;&quot;;
						try{ var r=eval(this.code);
							if(this.bufferedHTML.length || (typeof(r)===&quot;string&quot;)&amp;&amp;r.length)
								var s=this.parentNode.insertBefore(document.createElement(&quot;span&quot;),this.nextSibling);
							if(this.bufferedHTML.length)
								s.innerHTML=this.bufferedHTML;
							if((typeof(r)===&quot;string&quot;)&amp;&amp;r.length) {
								wikify(r,s,null,this.tiddler);
								return false;
							} else return r!==undefined?r:false;
						} catch(e){alert(e.description||e.toString());return false;}
					};
					link.setAttribute(&quot;title&quot;,tip||&quot;&quot;);
					var URIcode='javascript:void(eval(decodeURIComponent(%22(function(){try{';
					URIcode+=encodeURIComponent(encodeURIComponent(code.replace(/\n/g,' ')));
					URIcode+='}catch(e){alert(e.description||e.toString())}})()%22)))';
					link.setAttribute(&quot;href&quot;,URIcode);
					link.style.cursor=&quot;pointer&quot;;
					if (key) link.accessKey=key.substr(0,1); // single character only
				}
				else { // run inline script code
					var fixup=code.replace(/document.write\s*\(/gi,'place.innerHTML+=(');
					var code=&quot;function _out(place){&quot;+fixup+&quot;\n};_out(w.output);&quot;
					try { var out=eval(code); } catch(e) { out=e.description?e.description:e.toString(); }
					if (out &amp;&amp; out.length) wikify(out,w.output,w.highlightRegExp,w.tiddler);
				}
			}
			w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
		}
	}
} )
//}}}

// // Backward-compatibility for TW2.1.x and earlier
//{{{
if (typeof(wikifyPlainText)==&quot;undefined&quot;) window.wikifyPlainText=function(text,limit,tiddler) {
	if(limit &gt; 0) text = text.substr(0,limit);
	var wikifier = new Wikifier(text,formatter,null,tiddler);
	return wikifier.wikifyPlain();
}
//}}}

// // $(...) function: 'shorthand' convenience syntax for document.getElementById()
//{{{
if (typeof($)==&quot;undefined&quot;) { // avoid redefinition
function $() {
	var elements=new Array();
	for (var i=0; i&lt;arguments.length; i++) {
		var element=arguments[i];
		if (typeof element=='string') element=document.getElementById(element);
		if (arguments.length==1) return element;
		elements.push(element);
	}
	return elements;
}
}
//}}}</pre>
</div>
<div title="Introduction" modifier="YBA" created="200809161405" modified="200809161555" changecount="2" creator="YBA">
<pre>Ce TiddlyWiki est prêt pour vous guider dans votre projet SCRUM.
Il est convenu qu'un fichier TiddllyWiki correspond a un projet Scrum.
Les plugins pré-installé permettent de 
* gérer des backlogs (listes de UserStory triées par priorités)
* saisir et générer des burndown chart
* collaborer a plusieurs sur un même projet
Cependant en plus de ces outils spécifiques il est possible de gérer :
* Un backlog contenant des UserStory
* des sprints qui sont une selection de UserStory découpées en tâches

Pour en savoir plus sur Scrum :
* [[Concepts scrum]]
* Scrum en [[Dix étapes]]</pre>
</div>
<div title="MainMenu" modifier="YBA" created="200807211447" modified="200809221543" changecount="10">
<pre>GettingStarted
[[Démarrer]]
[[Configuration]]
[[Aide]]
[[Versionning]]
----
[[TaskBoard]]
[[Burdown]]</pre>
</div>
<div title="MonkeyTaggerMacro" modifier="YBA" created="200809121021" modified="200809220802" tags="systemConfig excludeSearch excludeLists" changecount="6">
<pre>/***
| Name:|''monkeyTagger''|
| Created by:|SaqImtiaz|
| Location:|http://tw.lewcid.org/|
| Version:|0.9 (08-Apr-2006)|
| Requires:|~TW2.07|

!About:
*an adaptation of TagAdderMacro for monkeyGTD and tagglytagging user, but could be useful to just about anyone!
*{{{&lt;&lt;monkeyTagger Project&gt;&gt;}}} gives a drop down list of all tags, tagged with Project.
*The list allows toggling of tags on the current tiddler.
*logging options for task management.

!Demo:
&lt;&lt;monkeyTagger Status&gt;&gt;

!Installation:
*Copy this tiddler to your TW with the systemConfig tag
*either copy the following to your ViewTemplate:
{{{&lt;div class='tagged' macro='monkeyTagger tagToTrack'&gt;&lt;/div&gt;}}}
or
*better yet, define your own toolbar class and add as many as you need to create a nice toolbar.
Eg:
{{{&lt;div class='toolbar' &gt;
&lt;span style=&quot;padding-right:0.15em;&quot; macro='monkeyTagger Project'&gt;&lt;/span&gt;
&lt;span style=&quot;padding-right:0.15em;&quot; macro='monkeyTagger Status'&gt;&lt;/span&gt;
&lt;span macro='toolbar -closeTiddler closeOthers +editTiddler permalink references jump'&gt;&lt;/span&gt;
&lt;/div&gt;}}}
 (adjust padding to taste)

!Usage:

''Syntax:''
|&gt;|{{{&lt;&lt;monkeyTagger source:&quot;sourcetag&quot; label:&quot;customlabel&quot; logging:&quot;true/false&quot; anchor:&quot;anchortext&quot;  arrow:&quot;true/false&quot;&gt;&gt;}}}|
|label:|quoted text to use as a customlabel|
|arrow:|add arrow to custom label, values are &quot;true&quot; or &quot;false&quot;|
|anchor:|quoted text to specify where to add logging text|
|logging:|enable logging of tags added (for task management), values are &quot;true&quot; or &quot;false&quot;|

the only parameter you ''have'' to pass is the source. When passing only one parameter, you can write either something like:
{{{&lt;&lt;monkeyTagger &quot;Project&quot;&gt;&gt;}}} or {{{&lt;&lt;monkeyTagger source:&quot;Project&quot;&gt;&gt;}}} for &lt;&lt;monkeyTagger Project&gt;&gt;
All other parameters are optional, and can be written in any order.

''Defaults:''
|label:|default label if not specified = source tag + arrow|
|arrow:|true |
|logging:|false |
|anchor:|none used by default, logging text added to end of tiddler |

''Examples:''
|custom label| {{{&lt;&lt;monkeyTagger source:&quot;Project&quot; label:&quot;customlabel&quot;&gt;&gt;}}} |&lt;&lt;monkeyTagger source:&quot;Project&quot; label:&quot;customlabel&quot;&gt;&gt;|
|custom label without arrow| {{{&lt;&lt;monkeyTagger source:&quot;Project&quot; label:&quot;customlabel&quot; arrow:&quot;false&quot;&gt;&gt;}}} |&lt;&lt;monkeyTagger source:&quot;Project&quot; label:&quot;customlabel&quot; arrow:&quot;false&quot;&gt;&gt;|
|logging enabled| {{{&lt;&lt;monkeyTagger source:&quot;Project&quot; logging:&quot;true&quot;}}} |&lt;&lt;monkeyTagger source:&quot;Project&quot; logging:&quot;true&quot;&gt;&gt;|
|logging enabled with anchor text|{{{&lt;&lt;monkeyTagger source:&quot;Project&quot; logging:&quot;true&quot; anchor:&quot;anchortext&quot;}}} |&lt;&lt;monkeyTagger source:&quot;Project&quot; logging:&quot;true&quot; anchor:&quot;anchortext&quot;&gt;&gt;|

''Tips:''
*Make sure your anchor text doesn't occur more than once in every tiddler, as the first instance will be used.
*I recommend using something like {{{/%StatusLog%/}}} as an invisible anchor.
*Use a tag based template, and add monkeyTagger macro's with logging enabled to the toolbar in just your taskmanagement templates.

!To Do:
*add sorting options if requested.
*''add exclude tag feature''!

!History
*Version 0.9: 
**changed to named parameters to make it more user friendly
**added option to disable/enable dropdown arrow in custom labels
**added logging option with anchor text.

!CODE
***/
//{{{

config.macros.monkeyTagger= {};
//config.macros.monkeyTagger.dropdownchar = (document.all?&quot;▼&quot;:&quot;▾&quot;); // the fat one is the only one that works in IE
config.macros.monkeyTagger.dropdownchar = &quot;▼&quot;; // uncomment previous line and comment this for smaller version in FF
config.macros.monkeyTagger.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
 var nAV = paramString.parseParams('test', null, true);

 if ((nAV[0].arrow)&amp;&amp;(nAV[0].arrow[0])=='false')
    var arrow=': ';
 else
     var arrow=': '+ config.macros.monkeyTagger.dropdownchar;

 if((nAV[0].source)&amp;&amp;(nAV[0].source[0])!='.')
        {var tagToTrack = nAV[0].source[0]}
 else if(params[0]&amp;&amp;(params[0]!='.'))
      {var tagToTrack = params[0]}
 else
       {return false;};
 var monkeylabel = ((nAV[0].label)&amp;&amp;(nAV[0].label[0])!='.')?nAV[0].label[0]+arrow: tagToTrack+arrow;
 var logmode = ((nAV[0].logging)&amp;&amp;(nAV[0].logging[0])!='.')?nAV[0].logging[0]: &quot;false&quot;;
 if ((nAV[0].anchor)&amp;&amp;(nAV[0].anchor[0])!='.')
    var anchor = nAV[0].anchor[0];
 var monkeytooltip=tagToTrack + ' :';


     if(tiddler instanceof Tiddler) {
	  	var title = tiddler.title;

                var addcomment = function(tiddler,newTag) {
                    var now = new Date();
                    var timeFormat= 'DD/0MM/YY 0hh:0mm';
                    var formattednow= now.formatString(timeFormat);
                    var txt=&quot;\n*''&quot;+tagToTrack+&quot;'' set as ''&quot;+newTag+&quot;'' on &quot;+formattednow;
                    if (anchor &amp;&amp; anchor!='.')
                       {var pos=tiddler.text.indexOf(anchor);
                       if (pos!=-1) {pos=pos + anchor.length}
                       else if (pos==-1) {pos=tiddler.text.length}}
                    else if (!anchor){var pos = tiddler.text.length;};

                    tiddler.set(null,tiddler.text.substr(0,pos)+txt+tiddler.text.substr(pos));
                    story.refreshTiddler(tiddler.title,null,true);
                    return false;
                }

                var ontagclick = function(e) {
                    if (!e) var e = window.event;
                    var tag = this.getAttribute(&quot;tag&quot;);
                    var t=store.getTiddler(title);
                    var thistiddlertags = story.getTiddlerField(title, 'tags').value.readBracketedList();
                    if (!t || !t.tags) return;
                    if (thistiddlertags.find(tag)==null) {
                       story.setTiddlerTag(title,tag,true);
                       if (logmode==&quot;true&quot;) {
                          addcomment(t,tag);
                       }
                    }
                    else {
                       story.setTiddlerTag(title,tag,false);
                    }
                    return false;
                };

                var onclick = function(e) {
                    if (!e) var e = window.event;
                    var popup = Popup.create(this);
                    var thistiddler=store.getTiddler(title);

                    var thistiddlertags = story.getTiddlerField(title, 'tags').value.readBracketedList();

                    var taggedarray = new Array();
                    var tagslabel = new Array();

                    var taggedtiddlers = store.getTaggedTiddlers(tagToTrack);
                    for (var t=0; t&lt;taggedtiddlers.length; t++){
                        var taggedtitle= ((taggedtiddlers[t]).title);
                        taggedarray.push(taggedtitle);}

                    for (var t=0; t&lt;taggedarray.length; t++){
                        var temptag = taggedarray[t];
                        if (thistiddlertags.find(temptag)==null) {
                           var temptag='[ ] '+ temptag;
                        }
                        else {
                           var temptag ='[x] '+ temptag;
                        }
                        tagslabel.push(temptag);
                    }

                    if(tagslabel.length == 0)
                       createTiddlyText(createTiddlyElement(popup,&quot;li&quot;),('no '+tagToTrack));
                    for (var t=0; t&lt;tagslabel.length; t++)
                    {
                       var theTag = createTiddlyButton(createTiddlyElement(popup,&quot;li&quot;),tagslabel[t],(&quot;toggle '&quot;+ ([taggedarray[t]]))+&quot;'&quot;,ontagclick);
                       theTag.setAttribute(&quot;tag&quot;,taggedarray[t]);
                    }
       Popup.show(popup,false);
       e.cancelBubble = true;
       if (e.stopPropagation) e.stopPropagation();
       return(false);
};
 //createTiddlyButton(place,monkeylabel,monkeylabel,onclick);

var createdropperButton = function(place){
var sp = createTiddlyElement(place,&quot;span&quot;,null,&quot;monkeytaggerbutton&quot;);
var theDropDownBtn = createTiddlyButton(sp,monkeylabel,monkeytooltip,onclick);
};

createdropperButton(place);
 }
};
setStylesheet(
 &quot;.toolbar .monkeytaggerbutton {margin-right:0em; border:0px solid #fff; padding:0px; padding-right:0px; padding-left:0px;}\n&quot;+
 &quot;.monkeytaggerbutton a.button {padding:2px; padding-left:2px; padding-right:2px;}\n&quot;+
// &quot;.monkeytaggerbutton {font-size:130%;}\n&quot;+
//&quot;.monkeytaggerbutton .button {color:#703;}\n&quot;+
 &quot;&quot;,
&quot;MonkeyTaggerStyles&quot;);

// Find an entry in an array. Returns the array index or null
// @Deprecated: Use indexOf instead
Array.prototype.find = function(item)
{
	var i = this.indexOf(item);
	return i == -1 ? null : i;
};


//}}}</pre>
</div>
<div title="NestedSlidersPlugin" modifier="YBA" created="200809100901" modified="200809220809" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|NestedSlidersPlugin|
|Source|http://www.TiddlyTools.com/#NestedSlidersPlugin|
|Documentation|http://www.TiddlyTools.com/#NestedSlidersPluginInfo|
|Version|2.4.6|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides||
|Options|##Configuration|
|Description|show content in nest-able sliding/floating panels, without creating separate tiddlers for each panel's content|
!!!!!Documentation
&gt;see [[NestedSlidersPluginInfo]]
!!!!!Configuration
&lt;&lt;&lt;
&lt;&lt;option chkFloatingSlidersAnimate&gt;&gt; allow floating sliders to animate when opening/closing
&gt;Note: This setting can cause 'clipping' problems in some versions of InternetExplorer.
&gt;In addition, for floating slider animation to occur you must also allow animation in general (see [[AdvancedOptions]]).
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.09.07 - 2.4.6 added removeOptionCookie() function for compatibility with [[CookieManagerPlugin]]
2008.06.07 - 2.4.5 in 'onmouseover' handler for 'open on hover' slider buttons,&lt;br&gt;use call() method when invoking document.onclick function (avoids error in IE)
|please see [[NestedSlidersPluginInfo]] for additional revision details|
2005.11.03 - 1.0.0 initial public release.  Thanks to RodneyGomes, GeoffSlocock, and PaulPetterson for suggestions and experiments.
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.nestedSliders = {major: 2, minor: 4, revision: 6, date: new Date(2008,9,7)};

// options for deferred rendering of sliders that are not initially displayed
if (config.options.chkFloatingSlidersAnimate===undefined)
	config.options.chkFloatingSlidersAnimate=false; // avoid clipping problems in IE

// default styles for 'floating' class
setStylesheet(&quot;.floatingPanel { position:absolute; z-index:10; padding:0.5em; margin:0em; \
	background-color:#eee; color:#000; border:1px solid #000; text-align:left; }&quot;,&quot;floatingPanelStylesheet&quot;);

// if removeOptionCookie() function is not defined by TW core, define it here.
if (window.removeOptionCookie===undefined) {
window.removeOptionCookie=function(cookie) {
	var ex=new Date(); ex.setTime(ex.getTime()-1000);  // immediately expire cookie
	document.cookie = cookie+&quot;=novalue; path=/; expires=&quot;+ex.toGMTString();
}
}

config.formatters.push( {
	name: &quot;nestedSliders&quot;,
	match: &quot;\\n?\\+{3}&quot;,
	terminator: &quot;\\s*\\={3}\\n?&quot;,
	lookahead: &quot;\\n?\\+{3}(\\+)?(\\([^\\)]*\\))?(\\!*)?(\\^(?:[^\\^\\*\\@\\[\\&gt;]*\\^)?)?(\\*)?(\\@)?(?:\\{\\{([\\w]+[\\s\\w]*)\\{)?(\\[[^\\]]*\\])?(\\[[^\\]]*\\])?(?:\\}{3})?(\\#[^:]*\\:)?(\\&gt;)?(\\.\\.\\.)?\\s*&quot;,
	handler: function(w)
		{
			lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
			lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = lookaheadRegExp.exec(w.source)
			if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart)
			{
				var defopen=lookaheadMatch[1];
				var cookiename=lookaheadMatch[2];
				var header=lookaheadMatch[3];
				var panelwidth=lookaheadMatch[4];
				var transient=lookaheadMatch[5];
				var hover=lookaheadMatch[6];
				var buttonClass=lookaheadMatch[7];
				var label=lookaheadMatch[8];
				var openlabel=lookaheadMatch[9];
				var panelID=lookaheadMatch[10];
				var blockquote=lookaheadMatch[11];
				var deferred=lookaheadMatch[12];

				// location for rendering button and panel
				var place=w.output;

				// default to closed, no cookie, no accesskey, no alternate text/tip
				var show=&quot;none&quot;; var cookie=&quot;&quot;; var key=&quot;&quot;;
				var closedtext=&quot;&gt;&quot;; var closedtip=&quot;&quot;;
				var openedtext=&quot;&lt;&quot;; var openedtip=&quot;&quot;;

				// extra &quot;+&quot;, default to open
				if (defopen) show=&quot;block&quot;;

				// cookie, use saved open/closed state
				if (cookiename) {
					cookie=cookiename.trim().slice(1,-1);
					cookie=&quot;chkSlider&quot;+cookie;
					if (config.options[cookie]==undefined)
						{ config.options[cookie] = (show==&quot;block&quot;) }
					show=config.options[cookie]?&quot;block&quot;:&quot;none&quot;;
				}

				// parse label/tooltip/accesskey: [label=X|tooltip]
				if (label) {
					var parts=label.trim().slice(1,-1).split(&quot;|&quot;);
					closedtext=parts.shift();
					if (closedtext.substr(closedtext.length-2,1)==&quot;=&quot;)	
						{ key=closedtext.substr(closedtext.length-1,1); closedtext=closedtext.slice(0,-2); }
					openedtext=closedtext;
					if (parts.length) closedtip=openedtip=parts.join(&quot;|&quot;);
					else { closedtip=&quot;show &quot;+closedtext; openedtip=&quot;hide &quot;+closedtext; }
				}

				// parse alternate label/tooltip: [label|tooltip]
				if (openlabel) {
					var parts=openlabel.trim().slice(1,-1).split(&quot;|&quot;);
					openedtext=parts.shift();
					if (parts.length) openedtip=parts.join(&quot;|&quot;);
					else openedtip=&quot;hide &quot;+openedtext;
				}

				var title=show=='block'?openedtext:closedtext;
				var tooltip=show=='block'?openedtip:closedtip;

				// create the button
				if (header) { // use &quot;Hn&quot; header format instead of button/link
					var lvl=(header.length&gt;5)?5:header.length;
					var btn = createTiddlyElement(createTiddlyElement(place,&quot;h&quot;+lvl,null,null,null),&quot;a&quot;,null,buttonClass,title);
					btn.onclick=onClickNestedSlider;
					btn.setAttribute(&quot;href&quot;,&quot;javascript:;&quot;);
					btn.setAttribute(&quot;title&quot;,tooltip);
				}
				else
					var btn = createTiddlyButton(place,title,tooltip,onClickNestedSlider,buttonClass);
				btn.innerHTML=title; // enables use of HTML entities in label

				// set extra button attributes
				btn.setAttribute(&quot;closedtext&quot;,closedtext);
				btn.setAttribute(&quot;closedtip&quot;,closedtip);
				btn.setAttribute(&quot;openedtext&quot;,openedtext);
				btn.setAttribute(&quot;openedtip&quot;,openedtip);
				btn.sliderCookie = cookie; // save the cookiename (if any) in the button object
				btn.defOpen=defopen!=null; // save default open/closed state (boolean)
				btn.keyparam=key; // save the access key letter (&quot;&quot; if none)
				if (key.length) {
					btn.setAttribute(&quot;accessKey&quot;,key); // init access key
					btn.onfocus=function(){this.setAttribute(&quot;accessKey&quot;,this.keyparam);}; // **reclaim** access key on focus
				}
				btn.setAttribute(&quot;hover&quot;,hover?&quot;true&quot;:&quot;false&quot;);
				btn.onmouseover=function(ev) {
					// optional 'open on hover' handling
					if (this.getAttribute(&quot;hover&quot;)==&quot;true&quot; &amp;&amp; this.sliderPanel.style.display=='none') {
						document.onclick.call(document,ev); // close transients
						onClickNestedSlider(ev); // open this slider
					}
					// mouseover on button aligns floater position with button
					if (window.adjustSliderPos) window.adjustSliderPos(this.parentNode,this,this.sliderPanel);
				}

				// create slider panel
				var panelClass=panelwidth?&quot;floatingPanel&quot;:&quot;sliderPanel&quot;;
				if (panelID) panelID=panelID.slice(1,-1); // trim off delimiters
				var panel=createTiddlyElement(place,&quot;div&quot;,panelID,panelClass,null);
				panel.button = btn; // so the slider panel know which button it belongs to
				btn.sliderPanel=panel; // so the button knows which slider panel it belongs to
				panel.defaultPanelWidth=(panelwidth &amp;&amp; panelwidth.length&gt;2)?panelwidth.slice(1,-1):&quot;&quot;;
				panel.setAttribute(&quot;transient&quot;,transient==&quot;*&quot;?&quot;true&quot;:&quot;false&quot;);
				panel.style.display = show;
				panel.style.width=panel.defaultPanelWidth;
				panel.onmouseover=function(event) // mouseover on panel aligns floater position with button
					{ if (window.adjustSliderPos) window.adjustSliderPos(this.parentNode,this.button,this); }

				// render slider (or defer until shown) 
				w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
				if ((show==&quot;block&quot;)||!deferred) {
					// render now if panel is supposed to be shown or NOT deferred rendering
					w.subWikify(blockquote?createTiddlyElement(panel,&quot;blockquote&quot;):panel,this.terminator);
					// align floater position with button
					if (window.adjustSliderPos) window.adjustSliderPos(place,btn,panel);
				}
				else {
					var src = w.source.substr(w.nextMatch);
					var endpos=findMatchingDelimiter(src,&quot;+++&quot;,&quot;===&quot;);
					panel.setAttribute(&quot;raw&quot;,src.substr(0,endpos));
					panel.setAttribute(&quot;blockquote&quot;,blockquote?&quot;true&quot;:&quot;false&quot;);
					panel.setAttribute(&quot;rendered&quot;,&quot;false&quot;);
					w.nextMatch += endpos+3;
					if (w.source.substr(w.nextMatch,1)==&quot;\n&quot;) w.nextMatch++;
				}
			}
		}
	}
)

function findMatchingDelimiter(src,starttext,endtext) {
	var startpos = 0;
	var endpos = src.indexOf(endtext);
	// check for nested delimiters
	while (src.substring(startpos,endpos-1).indexOf(starttext)!=-1) {
		// count number of nested 'starts'
		var startcount=0;
		var temp = src.substring(startpos,endpos-1);
		var pos=temp.indexOf(starttext);
		while (pos!=-1)  { startcount++; pos=temp.indexOf(starttext,pos+starttext.length); }
		// set up to check for additional 'starts' after adjusting endpos
		startpos=endpos+endtext.length;
		// find endpos for corresponding number of matching 'ends'
		while (startcount &amp;&amp; endpos!=-1) {
			endpos = src.indexOf(endtext,endpos+endtext.length);
			startcount--;
		}
	}
	return (endpos==-1)?src.length:endpos;
}
//}}}
//{{{
window.onClickNestedSlider=function(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	while (theTarget &amp;&amp; theTarget.sliderPanel==undefined) theTarget=theTarget.parentNode;
	if (!theTarget) return false;
	var theSlider = theTarget.sliderPanel;
	var isOpen = theSlider.style.display!=&quot;none&quot;;

	// toggle label
	theTarget.innerHTML=isOpen?theTarget.getAttribute(&quot;closedText&quot;):theTarget.getAttribute(&quot;openedText&quot;);
	// toggle tooltip
	theTarget.setAttribute(&quot;title&quot;,isOpen?theTarget.getAttribute(&quot;closedTip&quot;):theTarget.getAttribute(&quot;openedTip&quot;));

	// deferred rendering (if needed)
	if (theSlider.getAttribute(&quot;rendered&quot;)==&quot;false&quot;) {
		var place=theSlider;
		if (theSlider.getAttribute(&quot;blockquote&quot;)==&quot;true&quot;)
			place=createTiddlyElement(place,&quot;blockquote&quot;);
		wikify(theSlider.getAttribute(&quot;raw&quot;),place);
		theSlider.setAttribute(&quot;rendered&quot;,&quot;true&quot;);
	}
	// show/hide the slider
	if(config.options.chkAnimate &amp;&amp; (!hasClass(theSlider,'floatingPanel') || config.options.chkFloatingSlidersAnimate))
		anim.startAnimating(new Slider(theSlider,!isOpen,e.shiftKey || e.altKey,&quot;none&quot;));
	else
		theSlider.style.display = isOpen ? &quot;none&quot; : &quot;block&quot;;
	// reset to default width (might have been changed via plugin code)
	theSlider.style.width=theSlider.defaultPanelWidth;
	// align floater panel position with target button
	if (!isOpen &amp;&amp; window.adjustSliderPos) window.adjustSliderPos(theSlider.parentNode,theTarget,theSlider);
	// if showing panel, set focus to first 'focus-able' element in panel
	if (theSlider.style.display!=&quot;none&quot;) {
		var ctrls=theSlider.getElementsByTagName(&quot;*&quot;);
		for (var c=0; c&lt;ctrls.length; c++) {
			var t=ctrls[c].tagName.toLowerCase();
			if ((t==&quot;input&quot; &amp;&amp; ctrls[c].type!=&quot;hidden&quot;) || t==&quot;textarea&quot; || t==&quot;select&quot;)
				{ ctrls[c].focus(); break; }
		}
	}
	var cookie=theTarget.sliderCookie;
	if (cookie &amp;&amp; cookie.length) {
		config.options[cookie]=!isOpen;
		if (config.options[cookie]!=theTarget.defOpen) saveOptionCookie(cookie);
		else removeOptionCookie(cookie); // remove cookie if slider is in default display state
	}

	// prevent SHIFT-CLICK from being processed by browser (opens blank window... yuck!)
	// prevent clicks *within* a slider button from being processed by browser
	// but allow plain click to bubble up to page background (to close transients, if any)
	if (e.shiftKey || theTarget!=resolveTarget(e))
		{ e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation(); }
	Popup.remove(); // close open popup (if any)
	return false;
}
//}}}
//{{{
// click in document background closes transient panels 
document.nestedSliders_savedOnClick=document.onclick;
document.onclick=function(ev) { if (!ev) var ev=window.event; var target=resolveTarget(ev);

	if (document.nestedSliders_savedOnClick)
		var retval=document.nestedSliders_savedOnClick.apply(this,arguments);
	// if click was inside a popup... leave transient panels alone
	var p=target; while (p) if (hasClass(p,&quot;popup&quot;)) break; else p=p.parentNode;
	if (p) return retval;
	// if click was inside transient panel (or something contained by a transient panel), leave it alone
	var p=target; while (p) {
		if ((hasClass(p,&quot;floatingPanel&quot;)||hasClass(p,&quot;sliderPanel&quot;))&amp;&amp;p.getAttribute(&quot;transient&quot;)==&quot;true&quot;) break;
		p=p.parentNode;
	}
	if (p) return retval;
	// otherwise, find and close all transient panels...
	var all=document.all?document.all:document.getElementsByTagName(&quot;DIV&quot;);
	for (var i=0; i&lt;all.length; i++) {
		 // if it is not a transient panel, or the click was on the button that opened this panel, don't close it.
		if (all[i].getAttribute(&quot;transient&quot;)!=&quot;true&quot; || all[i].button==target) continue;
		// otherwise, if the panel is currently visible, close it by clicking it's button
		if (all[i].style.display!=&quot;none&quot;) window.onClickNestedSlider({target:all[i].button}) 
	}
	return retval;
};
//}}}
//{{{
// adjust floating panel position based on button position
if (window.adjustSliderPos==undefined) window.adjustSliderPos=function(place,btn,panel) {
	if (hasClass(panel,&quot;floatingPanel&quot;)) {
		var rightEdge=document.body.offsetWidth-1;
		var panelWidth=panel.offsetWidth;
		var left=0;
		var top=btn.offsetHeight; 
		if (place.style.position==&quot;relative&quot; &amp;&amp; findPosX(btn)+panelWidth&gt;rightEdge) {
			left-=findPosX(btn)+panelWidth-rightEdge; // shift panel relative to button
			if (findPosX(btn)+left&lt;0) left=-findPosX(btn); // stay within left edge
		}
		if (place.style.position!=&quot;relative&quot;) {
			var left=findPosX(btn);
			var top=findPosY(btn)+btn.offsetHeight;
			var p=place; while (p &amp;&amp; !hasClass(p,'floatingPanel')) p=p.parentNode;
			if (p) { left-=findPosX(p); top-=findPosY(p); }
			if (left+panelWidth&gt;rightEdge) left=rightEdge-panelWidth;
			if (left&lt;0) left=0;
		}
		panel.style.left=left+&quot;px&quot;; panel.style.top=top+&quot;px&quot;;
	}
}
//}}}
//{{{
// TW2.1 and earlier:
// hijack Slider stop handler so overflow is visible after animation has completed
Slider.prototype.coreStop = Slider.prototype.stop;
Slider.prototype.stop = function()
	{ this.coreStop.apply(this,arguments); this.element.style.overflow = &quot;visible&quot;; }

// TW2.2+
// hijack Morpher stop handler so sliderPanel/floatingPanel overflow is visible after animation has completed
if (version.major+.1*version.minor+.01*version.revision&gt;=2.2) {
	Morpher.prototype.coreStop = Morpher.prototype.stop;
	Morpher.prototype.stop = function() {
		this.coreStop.apply(this,arguments);
		var e=this.element;
		if (hasClass(e,&quot;sliderPanel&quot;)||hasClass(e,&quot;floatingPanel&quot;)) {
			// adjust panel overflow and position after animation
			e.style.overflow = &quot;visible&quot;;
			if (window.adjustSliderPos) window.adjustSliderPos(e.parentNode,e.button,e);
		}
	};
}
//}}}</pre>
</div>
<div title="NewHerePlugin" modifier="YBA" created="200809151327" modified="200809220809" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name:|NewHerePlugin|
|Description:|Creates the new here and new journal macros|
|Version:|3.0 ($Rev: 3861 $)|
|Date:|$Date: 2008-03-08 10:53:09 +1000 (Sat, 08 Mar 2008) $|
|Source:|http://mptw.tiddlyspot.com/#NewHerePlugin|
|Author:|Simon Baird &lt;simon.baird@gmail.com&gt;|
|License|http://mptw.tiddlyspot.com/#TheBSDLicense|
***/
//{{{
merge(config.macros, {
	newHere: {
		handler: function(place,macroName,params,wikifier,paramString,tiddler) {
			wikify(&quot;&lt;&lt;newTiddler &quot;+paramString+&quot; tag:[[&quot;+tiddler.title+&quot;]]&gt;&gt;&quot;,place,null,tiddler);
		}
	},
	newJournalHere: {
		handler: function(place,macroName,params,wikifier,paramString,tiddler) {
			wikify(&quot;&lt;&lt;newJournal &quot;+paramString+&quot; tag:[[&quot;+tiddler.title+&quot;]]&gt;&gt;&quot;,place,null,tiddler);
		}
	}
});

//}}}

</pre>
</div>
<div title="Objectif" modifier="YBA" created="200809111408" modified="200809171254">
<pre>Précisez l'objectif du projet, dans quel contexte il s'inscrit, et ses contraintes
----
Création d'un nouveau module pour remplacer le module &quot;sillaj&quot; qui permettait de saisir les RTA et de construire des rapports sur l'activité de chaque employé et sur l'affectation de ressource sur les projets
Le module comporte un module d'administration ainsi que 3 sous-modules permettant la création des types de contenu &quot;projet&quot;, &quot;activité&quot; et &quot;tache&quot;</pre>
</div>
<div title="Paramètres" modifier="YBA" created="200809191145" modified="200809221155" changecount="2">
<pre>* Définissez le titre de votre projet SiteTitle &amp; SiteSubtitle
* Définissez les [[Features]] et classez les par thème ansi que l'[[Objectif]]
* Mettez au point une liste préliminaire de Release : [[Plan de release]]
* Definissez la notion de [[Fini]]
* Définissez les [[SprintDays]] qui seront utilisés pour le Burdownchart</pre>
</div>
<div title="PasswordOptionPlugin" modifier="YBA" created="200809160802" modified="200809220810" tags="systemConfig Upload excludeSearch excludeLists" changecount="3">
<pre>/***
|''Name:''|PasswordOptionPlugin|
|''Description:''|Extends TiddlyWiki options with non encrypted password option.|
|''Version:''|1.0.2|
|''Date:''|Apr 19, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#PasswordOptionPlugin|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (Beta 5)|
***/
//{{{
version.extensions.PasswordOptionPlugin = {
	major: 1, minor: 0, revision: 2, 
	date: new Date(&quot;Apr 19, 2007&quot;),
	source: 'http://tiddlywiki.bidix.info/#PasswordOptionPlugin',
	author: 'BidiX (BidiX (at) bidix (dot) info',
	license: '[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D]]',
	coreVersion: '2.2.0 (Beta 5)'
};

config.macros.option.passwordCheckboxLabel = &quot;Save this password on this computer&quot;;
config.macros.option.passwordInputType = &quot;password&quot;; // password | text
setStylesheet(&quot;.pasOptionInput {width: 11em;}\n&quot;,&quot;passwordInputTypeStyle&quot;);

merge(config.macros.option.types, {
	'pas': {
		elementType: &quot;input&quot;,
		valueField: &quot;value&quot;,
		eventName: &quot;onkeyup&quot;,
		className: &quot;pasOptionInput&quot;,
		typeValue: config.macros.option.passwordInputType,
		create: function(place,type,opt,className,desc) {
			// password field
			config.macros.option.genericCreate(place,'pas',opt,className,desc);
			// checkbox linked with this password &quot;save this password on this computer&quot;
			config.macros.option.genericCreate(place,'chk','chk'+opt,className,desc);			
			// text savePasswordCheckboxLabel
			place.appendChild(document.createTextNode(config.macros.option.passwordCheckboxLabel));
		},
		onChange: config.macros.option.genericOnChange
	}
});

merge(config.optionHandlers['chk'], {
	get: function(name) {
		// is there an option linked with this chk ?
		var opt = name.substr(3);
		if (config.options[opt]) 
			saveOptionCookie(opt);
		return config.options[name] ? &quot;true&quot; : &quot;false&quot;;
	}
});

merge(config.optionHandlers, {
	'pas': {
 		get: function(name) {
			if (config.options[&quot;chk&quot;+name]) {
				return encodeCookie(config.options[name].toString());
			} else {
				return &quot;&quot;;
			}
		},
		set: function(name,value) {config.options[name] = decodeCookie(value);}
	}
});

// need to reload options to load passwordOptions
loadOptionsCookie();

/*
if (!config.options['pasPassword'])
	config.options['pasPassword'] = '';

merge(config.optionsDesc,{
		pasPassword: &quot;Test password&quot;
	});
*/
//}}}</pre>
</div>
<div title="Plan de release" modifier="YBA" created="200808181415" modified="200809171254">
<pre>Listez les différentes releases prévues avec les fonctionnalités qu'elles doivent implémenter</pre>
</div>
<div title="Populate/UpdateListPlugin" modifier="YBA" created="200809181610" modified="200809221514" tags="systemConfig excludeSearch excludeLists IDEIA" changecount="18">
<pre>/***
|''Name''|[[Populate/UpdateListPlugin]]|
|''Description''|Populate or update a sorted list by getting the tiddlers filtered by tags, wanted and unwanted.|
|''Author''|[[Jean-Cédric THÉROND - IDEIA &lt;jctherond@ideia.fr&gt;|mailto:Jean-Cédric%20THÉROND&lt;jctherond@ideia.fr&gt;?subject=Plugin%20updatePopulate%20TW]]|
|''Contributors''|[[YBA - IDEIA &lt;ybabel@ideia.fr&gt;|mailto:Y.%20BABEL&lt;ybabel@ideia.fr&gt;?subject=Plugin%20updatePopulate%20TW]]|
|''Version''|0.8//b//|
|''Date''|2008.09.17|
|''Status''|@@font-variant: small-caps;font-size:1.2em;stable@@|
|''Source''|//void//|
|''CodeRepository''|//void//|
|''Copyright''|jean-Cédric THÉROND - IDEIA|
|''License''|[[GNU/GPL3+|http://www.gnu.org/licenses/gpl-3.0.html]]|
|''CoreVersion''|2.4|
|''Requires''|[[SortListPlugin]]|
|''Overrides''|//void//|
|''Feedback''|jctheron@ideia.fr|
|''Documentation''|[[Populate/UpdateListPluginDocumentation]]|
|''Keywords''|sorted list populate update filter tag|

!Code
***/
//{{{
config.macros.PopulateUpdateList = {
	label: &quot;Update Backlog&quot;,
	prompt: &quot;Update a BackLog&quot;,
	warning: &quot;Do you really want to populate/update this backlog ?&quot;,

	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
  	        var wantedTags=&quot;&quot;;
	        var unwantedTags=&quot;&quot;;
		var p=params.shift();

		while (p) {
			if (p.substr(0,5).toUpperCase()=='WANT:') {
				var wantedTags=p.substr(5);
			}
			else if (p.substr(0,9).toUpperCase()=='DONTWANT:') {
				var unwantedTags=p.substr(9);
			}
			p=params.shift();
		}
		this.createPopulateUpdateListButton(wantedTags, unwantedTags, place);
	},

	createPopulateUpdateListButton : function(wantedTags, unwantedTags, place){
    		var title= story.findContainingTiddler(place).id.substr(7);
		var btn = createTiddlyButton(place, this.label, this.prompt, this.onclickPopulateUpdateListButton);
                btn.setAttribute(&quot;wanted&quot;, wantedTags);
                btn.setAttribute(&quot;unwanted&quot;, unwantedTags);
                btn.setAttribute(&quot;tiddler.title&quot;, title);
		return false;
	},

	onclickPopulateUpdateListButton : function(e){
               var title = this.getAttribute(&quot;tiddler.title&quot;);
		var tiddlerDeTravail =  store.getTiddler(title);
		if(confirm(config.macros.PopulateUpdateList.warning)) {
		var wantedTags = this.getAttribute(&quot;wanted&quot;);
		var unwantedTags = this.getAttribute(&quot;unwanted&quot;);
		if (wantedTags){wantedTags = wantedTags.replace(/,/gi, &quot;&quot;).readBracketedList();}
		if (unwantedTags){unwantedTags = unwantedTags.replace(/,/gi, &quot;&quot;).readBracketedList();}

		/**
		 * for(var i = 0; i &lt; wantedTags.length; i++){displayMessage('wanted : '+wantedTags[i]);}
		 * for(var i = 0; i &lt; unwantedTags.length; i++){displayMessage('unwanted : '+unwantedTags[i]);}
		**/

		var results = new Array();
		store.forEachTiddler(function(title,tiddler) {
			if (tiddler.tags.containsAny(wantedTags) &amp;&amp; !tiddler.tags.containsAny(unwantedTags)){
				//var dateDiff = (parseInt(tiddler.modified.getTime())-parseInt(invokTiddler.modified.getTime()))/1000;
				//if (dateDiff &gt; 0){
				results.unshift(tiddler.title);
				//}
			}
		});

		//for (var i = 0; i &lt; results.length; i++){alert('which ?'+ results[i]);}


		var tags=[]; // an empty array
		var fields={}; // an empty object
		var who=config.options.txtUserName; // current username
		var when=new Date(); // current timestamp

		var original = tiddlerDeTravail.text.split('\n');
		var bottom = original.splice(original.indexOf('----'),original.length);
		results = results.mergeUnique(original.linkify()).linkify().concat(bottom);
		var output = results.join('\n');
		tags=tiddlerDeTravail.tags; fields=tiddlerDeTravail.fields;
                store.saveTiddler(tiddlerDeTravail.title,tiddlerDeTravail.title,output,who,when,tags,fields);
		}
		return false;
	}

};
//}}}

//{{{
/**
 * Array.unique( strict ) - Remove duplicate values
 * source: http://4umi.com/web/javascript/array.php#unique
 * heavyly modded by Jean-Cédric THÉROND &lt;jctherond@ideia.fr&gt;
**/
Array.prototype.unique = function() {
var a=[], b = this, L = b.length, i, j=0;

for(i=0;i&lt;L;i++) {
	if(b[i] != '\n'){
		a.pushUnique(b[i],true);
	}
}
return a;
};
//}}}

//{{{
Array.prototype.linkify = function() {
	var a = this,L = a.length,i;
	for(i=0;i&lt;L;i++){
		if ( (a[i].indexOf('&lt;&lt;',0) == -1) &amp;&amp; (a[i].indexOf('[[',0) == -1) ){
			a[i] = '[['+a[i]+']]';
		}
	}
	return a;
}
//}}}

//{{{
Array.prototype.mergeUnique = function(items){

	var p, a=this.linkify(), aL = this.length, b=items, bL=items.length, i;

	for (i=0 ; i &lt; aL ; i++){
		if (a[i] != undefined){
			p = -1;
			p = b.indexOf(a[i]);
			if(p &gt; -1){
				a.splice(i,1,'DEAD');
			}
		}
	}

	
	for (i=0 ; i &lt; a.length ; i++){
		if(a[i] != 'DEAD'){
			b.push(a[i]);
		}
	}
	return b;
}
//}}}</pre>
</div>
<div title="Populate/UpdateListPluginDocumentation" modifier="YBA" created="200809181610" modified="200809220810" tags="IDEIA" changecount="9">
<pre>!Description
populate or update a sorted list by getting the tiddlers filtered by tags, wanted and unwanted.
!Notes
*Never call a task or a userStory &quot;DEAD&quot;, or else she'll buried.
*__Don't__ search tiddler with __space containing tags__ !
!Usage
{{{
&lt;&lt;PopulateUpdateList want:'tag1',&quot;tag2&quot; dontwant:&quot;tag3&quot;,'tag4'&gt;&gt;
}}}
!!Parameters
*want:comma separated list of quoted tags __without space__.
*dontwant:comma separated list of quoted tags __without space__.
!!Examples
@@color:red;font-size:1.3em;font-weight:bolder;ONLY IN A SortList TIDDLER@@
!Configuration Options
//void//
!Revision History
&gt;v0.8 (2008-09-19) : Now it preserves the order of tasks and adds those found at the end.
&gt;v0.3 (2008-09-17)
!To Do
//Improve (&quot;space containing tags&quot; search will be a ''great'' improvement)//</pre>
</div>
<div title="RapportBenchGlobal" modifier="YBA" created="200811201105" modified="200811261501" tags="doc" changecount="20">
<pre>http://buytaert.net/drupal-5-performance
* 2000 users
* 15 + 250 taxonomy
* 5000 + 2 node avec path alias
! sur machine virtuelle seule - 
* drupal-test 192.168.2.44
ab -c 20 -t 30 http://192.168.2.44/test/
# install par défaut drupal 6.6 sur KVM (1 coeur) U810 : 3.16, 3.95
# avec APC : 9.22, 8.38
# avec le cache normal de drupal : 169.28, 181.82, 179.24
# avec le cache agressif + block cache + optimize css + optimze javascript: 207.49, 225.79, 222.74
! sur double machine virtuelle (1 double coeur)
* drupal-sql 192.168.2.39
* drupal-php-1 192.168.2.38
ab -c 20 -t 30 http://192.168.2.38/test/
# install par defaut sur 2 KVM : 3.92, 5.23, 5.40 (la charge de mysql ne dépasse pas 10%)
# avec APC : 9.62, 10.93, 10.63
# avec le cache normal de drupal : 180.78, 200.24, 199.60, 201.31
# avec cache agressif : 193.56, 208.53, 215.65, 214.48 (drupal-sql a 20%)
! sur double machine physique
* drupal-sql 192.168.2.28
* drupal-php-1 192.168.2.38
ab -c 20 -t 30 http://192.168.2.38/test/
# (avec APC, sans cache) : 7.74, 8.36, 8.68
# avec le cache normal : 160.79, 184.19, 183.43
! avec load balancing
* drupal-sql 192.168.2.28
* drupal-php-1 192.168.2.38
* drupal-php-2 192.168.2.32
* drupal-lb 192.168.2.33
ab -c 20 -t 30 http://192.168.2.33/test/
# sans php2, avec cache : 200.99, 189.70, 199.27  (loadBalancer a 20%) : quasi équivalent sans le LB
# sans php2, sans cache : 9.12, 9.05, 9.00
# avec php2, sans cache : 8.03, 9.98, 10.06, 9.74, 9.88 (très léger gain)
# avec php2, avec cache : 128.55, 117.62, 133.20 (drupal php 1 a 20%)
! test de concurrence
* 10 php / lb : (176.93, 168.14) / (162.30, 136.04, 139.69)
* 20 php / lb : (183.96,187.99) / (137.77,127.12, 139.71)
* 30 php / lb : (174.33, 182.32) / (126.84, 136.98, 138.98)
* 40 php / lb : (173.98, 184.77) / (129.04, 133.59)
* 50 php / lb : (165.43, 179.47) / (129.76, 137.23)
* 55 php / lb : (176.02, 170.82) / (135.26, 137.00)
* 80 php / lb : (174.88
* 100 php / lb : 164.24
* 150 php / lb : 141.46-5 / 127.28, 
* 200 php / lb : 112.09-42 / 127.56, 107.52
* 250 php / lb : 115.15-58, 92.37-93 / 87.15, 100.90
* 300 php / lb : 124,25-44, 74.14-136 / 92.16, 92.83, 103.83
* 350 php / lb : / 92.63, 103.06
* 400 php / lb : 152.37-11, 86.74-95, 94.30-112 / 91.45
* 450 php / lb : 125.12-37, 91.63-131 / 86.69
* 500 php / lb : / err, err
sans le cache :
* 50 php / lb : 6.37 / 5.26
* 60 php / lb :  22.85-501/ timeout, timeout
* 75 php / lb : 21.10-478 / timeout
! avec swapspace (sur les 2 machines php)
sans le cache drupal :
* 50 php / lb : 5.38, 5.86 / 4.49
* 60 php / lb : 8.26, 6.32 / 1.68, 1.65
* 75 php / lb : 4.16, 7.17 / err
* 80 php / lb : 5.20 /
avec le cache drupal 
* 100 php / lb : 76.76-283, 59.33-534 / 14.99-1
* 250 php / lb : 53.23-543 / 58.33, err
* 300 php / lb : 
* 400 php / lb : 
* 450 php / lb : 
* 500 php / lb : 

! conclusions
* le cache drupal = X15
* cache agressif = 15% de plus
* le load balancing = perte de perf (je pige pas pourquoi !), mais supporte mieux la concurrence, de plus ne fonctionne véritablement qu'avec le cache, sinon c'est pire. 
* le réseau (100MBps) influence peu mais ralenti sensiblement.
* difficulté des tests a cause de la gestion de la mémoire / swap, perf parfois hors norme</pre>
</div>
<div title="RapportGlobalPreliminaires" modifier="YBA" created="200811190920" modified="200811191029" tags="doc" changecount="6">
<pre>* test des machines virtuelles, de l'overhead
** KVM et Xen sont de la paravirtualisation, pas de différence avec une machine normale
** VBox et VMWare ont un léger surplus
** KVM a été choisi : intégré a Linux, très dynamique et facile a mettre en oeuvre
** attention au dualcore, les machines virtuelles peuvent tourner sur un seul coeur
* test des différents module de cache pour drupal
** augmente de beaucoup les performances
** apc fait aussi le cache en mémoire, memcache améliore les perfs aussi
* outils externes
** squid amérliore les perfs, permet de faire de la répartition de charges aussi (roundrobin) X3 par rapport au cache drupal, X100 globalement par rapport au &quot;nu&quot;
** modproxy : répartition de charges entre apache (qui consomme plus que mysql), double les perfs
** mysqlproxy : avec la réplication, fait un mini-cluster pour la monté en charge
** test final : mise en place de 4 machines virtuelles sur 2 machines doublecore en réseau. Le réseau est un facteur limitant important.
** clustering : peu de solutions (mysql cluster, postgre-R, Raidb C-JDBC, sequoia)
* outils d'analyse
** outils de stress : ab, autobench (fait des tests auto) pas toujours facile a comprendre le résultat
** outils de monitoring : le basique moniteur système montre que quand les accès concurrents augmentent beaucoup, et que le swap est utilisé a 100% ca commence a planter
* difficultés
** se repérer dans la jungle au départ
** tutos pas toujours a jour ou complets
** qq bugs sur les dernières versions
* ce qui reste a creuser
** outils de stress et d'analyse
** segmentation, sharding, cluster</pre>
</div>
<div title="RapportTestSquid" modifier="YBA" created="200811041601" modified="200811051451" tags="doc" changecount="2">
<pre>!Test de squid
test avec un site sous drupal 6

!Attention
pour activer le cache squid il faut activer le cache drupal (le cache doit rester activé pour les headers)

!Configuration du site
* dans setting.php
** préciser $base_url, $cookie_domain
** dans $conf 'reverse_proxy' =&gt; TRUE et 'resverse_proxy_addresses' =&gt; array( ipduproxy,)
* configurer les sites dispo dans apache en conséquence

!Configuration de squid
laisser le port 3128 par défaut (un port par site?)
* pour modifier a configuration de squid manuellement il faut éditer le fichier /etc/squid/squid.conf (ou  /etc/squid3/squid.conf pour la version 3)
** ajouter la ligne http_port 3128 accel defalutsite=exemple.com transparent
** ajouter les accès avec la ligne acl allclient src 0.0.0.0-255.255.255.255 pour crer un groupe comprenant toutes les IP
** ajouter les autorisations pour le groupe allclient en ajoutant les lignes http_access allclient et icp_access allclient
* avec webmin il faut tout d'abord verifier la configuration dans «&amp;nbsp;module config&amp;nbsp;» lien en haut de la page principale de squid proxy server
** dans port and networking configurer,  proxy adresses and ports avec 3128 en port et  accel defalutsite=exemple.com transparent dans les options du port (laisser hostname/IP adresses à all)
** aller dans accès contrôle et créer une nouvelle acl «&amp;nbsp;client adress&amp;nbsp;» et affecter tout les adresses possibles ( de 0.0.0.0 à 255.255.255.255)
** ajouter les autorisations dans l'onglet proxy restrictions et ICP restrictions ( allow allclient )

tout le reste est configuré par défaut

!Tests de performances
pour activer le cache squid il faut activer le cache drupal (le cache doit rester activé pour les headers)
les tests sont faits avec 50 accès concurrents pendant 20s
* test sans aucun cache : 20 rps
* test avec cache drupal: 315 rps
* test avec cache squid: 1200 rps

! Fausses pistes et superflu
* Il n'est pas nécessaire de créer une hiérarchie pour le cache ni de le raccrocher à un parent ou frère existant ( cache_peer )

! à vérifier
* Le cachemanager n'est pas obligatoire
* Est ce que squid à les même performance sur drupal 6 avec ou sans le patch des header
 
! références
* http://2bits.com/articles/increasing-drupals-speed-squid-caching-reverse-proxy.html
* http://wiki.squid-cache.org
</pre>
</div>
<div title="RapportTestsLoadBalancer" modifier="YBA" created="200811131240" modified="200811181303" tags="doc" changecount="23">
<pre>! Étape 1,  le 7 novembre
attention le virtual manager sous ubuntu, pour supporter le réseau, doit être lancé en root
''séparation du serveur apache et mysql en 2 machines virtuelles''
serveur php avec le site drupal et serveur mysql avec seulement la base
* préciser sur le serveur mysql son ip en bind adress dans /etc/mysql/my.cnf
* et autoriser la connexion d'un utilisateur venant du serveur php (ou root provenant de tous les serveur çà va plus vite )
* résultat : 2.39 rps
suivre le tuto du lien suivant sans se préoccuper du nfs pour les données : http://www.johnandcailin.com/blog/john/scaling-drupal-step-one-dedicated-data-server

Etape 1-2
on place les données sur le serveur MySQL
* créer un partage nfs pour files et un lien symbolique du point de montage à site/default/files
* résultat : idem
on utilise le tutorial de l'étape 1 : http://www.johnandcailin.com/blog/john/scaling-drupal-step-one-dedicated-data-server

! Etape 2, le 12 novembre
''load balancing du serveur php en 2 apache''
on suit le tutorial : http://www.johnandcailin.com/blog/john/scaling-drupal-step-two-sticky-load-balancing-apache-modproxy

* duplication de la machine virtuelle php
* Load balancer avec un des 2 serveur php =&gt; echec
* Load balancer séparé, ne pas oublier de configurer le balance manager dans le vhost default d'apache (voir la doc de mod_proxy_balancer sur le site d'apache : http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html tout en bas)
* au final 4 mv (1lb, 2 php avec drupal, 1mysql avec les fichiers (files))
**  drupal-mysql
**  drupal-php-lb (loadbalancer)
**  drupal-php
**  drupal-php-2
* Configurer le setting.php des sites drupal pour les cookies de session et le nom de domaine
* Ajouter un vhost dans chaque machine serveur drupal pour le load balancer
* Ajouter les règles de réécriture pour les cookies dans le vhost de chaque machines serveur
* Configurer le vhost de la machine lb pour spécifier les membres de la balance, il est important de spécifier des hostnames plutôt que des adresses ip car on aurait que le fichier /etc/hosts de la machine à  modifier si les machines virtuelles changent d'ip
* Attention à lancer la machine drupal-mysql en premier (relancer mysql si nécessaire) c'est important pour le partage nfs des fichiers de drupal, la base pouvant être relancée plus facilement
* Attention à vérifier si  les IP n'ont pas bougé sinon modifier les fichiers /etc/hosts des 4 machines en conséquence
* Attention lors d'une trop forte charge, les machines virtuelles on tendance à figer

! Etape 3, du 13 nov au 14 nov
''load balancing du serveur mysql''
!! Réplication de base
on suit le tuto de  : http://sangokode.wordpress.com/2007/12/24/mysql-proxy-rw-splitting-replication-mysql/
car plus complet
* duplication de la machine virtuelle drupal-sql en drupal-sql-cache
!!! configuration du maître :
* dans le  fichier /etc/mysl/my.cnf 
** ajout de la base à répliquer sur la ligne binlog-do-db: binlog-do-db=test6
** on précise l'endroit ou mysql stockera ses logs pour le réplication en décommentant  la ligne log_bin
** decommenter la ligne server-id=1 
* ouvrir une session mysql pour creer le user qui sera utilisé par le ou les esclaves il faut lui accorder au moins le droit réplication
* afficher le statut du Master avec SHOW MASTER STATUS; et noter les champs files et position
!!! configuration d'un esclave 
* dans le  fichier /etc/mysl/my.cnf 
** ajout de la base à répliquer avec la ligne replicate-do-db=test6
** ajouter la ligne server-id=2 (ou autre chiffre &gt;2 pour les autres esclaves) 
** enfin avec master-user=slave, master-password=motdepasse, master-connect-retry=60 preciser le lodin et le mot de passe du user utilisé par les esclaves
* ouvrir une session mysql et lancer la requete : SLAVE STOP;
* puis  :
//{{{
CHANGE MASTER TO MASTER_HOST='adresse ip du maitre', MASTER_USER=' ',
MASTER_PASSWORD='motdepasse', MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=183;
//}}}
en remplassant biensur avec les bonnes valeurs de file et position récupérées dans le maitre
!! Load Balancing
il y a un bug dans la version 0.6.1 il faut prendre la version 0.7 en recompilant le SVN '' recompilation mysqlproxy''
* le bug http://bugs.mysql.com/bug.php?id=34793 serait corrigé par http://forge.mysql.com/wiki/MySQL_Proxy_Compiling
* ajouter les packets subservion, buildessential, flex, libglib2.0-dev, libmysqlclient15-dev 
* corriger le bug en lacant la commande suivante : http://forums.mysql.com/read.php?146,170597,170602#msg-170602
//{{{
/usr/local/sbin/mysql-proxy --proxy-backend-addresses=192.168.2.28:3306 --log-level=debug --proxy-lua-script=/usr/local/share/mysql-proxy/log.lua
//}}}
* appliquer le patch : http://bugs.mysql.com/bug.php?id=38419
//{{{
/usr/local/sbin/mysql-proxy --proxy-backend-addresses=192.168.2.28:3306 --proxy-read-only-backend-addresses=192.168.2.42:3306 --log-level=debug --proxy-lua-script=/usr/local/share/mysql-proxy/log.lua --proxy-lua-script=/usr/local/share/mysql-proxy/rw-splitting.lua
//}}}</pre>
</div>
<div title="RefreshCommand" modifier="YBA" created="200809151421" tags="systemConfig" changecount="1">
<pre>/***
***/

//{{{

config.commands.refresh = {
 text: 'refresh',
 tooltip: 'Refresh this tiddler',
 handler: function(e,src,title) {
  clearMessage();
  story.refreshTiddler(title,false,true); // force=true
  return false;
 }
};

//}}}</pre>
</div>
<div title="RenameTagsPlugin" modifier="YBA" created="200809241531" tags="systemConfig excludeSearch excludeLists" changecount="1">
<pre>/***
|Name:|RenameTagsPlugin|
|Description:|Allows you to easily rename or delete tags across multiple tiddlers|
|Version:|3.0 ($Rev: 5501 $)|
|Date:|$Date: 2008-06-10 23:11:55 +1000 (Tue, 10 Jun 2008) $|
|Source:|http://mptw.tiddlyspot.com/#RenameTagsPlugin|
|Author:|Simon Baird &lt;simon.baird@gmail.com&gt;|
|License|http://mptw.tiddlyspot.com/#TheBSDLicense|
Rename a tag and you will be prompted to rename it in all its tagged tiddlers.
***/
//{{{
config.renameTags = {

	prompts: {
		rename: &quot;Rename the tag '%0' to '%1' in %2 tidder%3?&quot;,
		remove: &quot;Remove the tag '%0' from %1 tidder%2?&quot;
	},

	removeTag: function(tag,tiddlers) {
		store.suspendNotifications();
		for (var i=0;i&lt;tiddlers.length;i++) {
			store.setTiddlerTag(tiddlers[i].title,false,tag);
		}
		store.resumeNotifications();
		store.notifyAll();
	},

	renameTag: function(oldTag,newTag,tiddlers) {
		store.suspendNotifications();
		for (var i=0;i&lt;tiddlers.length;i++) {
			store.setTiddlerTag(tiddlers[i].title,false,oldTag); // remove old
			store.setTiddlerTag(tiddlers[i].title,true,newTag);  // add new
		}
		store.resumeNotifications();
		store.notifyAll();
	},

	storeMethods: {

		saveTiddler_orig_renameTags: TiddlyWiki.prototype.saveTiddler,

		saveTiddler: function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created) {
			if (title != newTitle) {
				var tagged = this.getTaggedTiddlers(title);
				if (tagged.length &gt; 0) {
					// then we are renaming a tag
					if (confirm(config.renameTags.prompts.rename.format([title,newTitle,tagged.length,tagged.length&gt;1?&quot;s&quot;:&quot;&quot;])))
						config.renameTags.renameTag(title,newTitle,tagged);

					if (!this.tiddlerExists(title) &amp;&amp; newBody == &quot;&quot;)
						// dont create unwanted tiddler
						return null;
				}
			}
			return this.saveTiddler_orig_renameTags(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created);
		},

		removeTiddler_orig_renameTags: TiddlyWiki.prototype.removeTiddler,

		removeTiddler: function(title) {
			var tagged = this.getTaggedTiddlers(title);
			if (tagged.length &gt; 0)
				if (confirm(config.renameTags.prompts.remove.format([title,tagged.length,tagged.length&gt;1?&quot;s&quot;:&quot;&quot;])))
					config.renameTags.removeTag(title,tagged);
			return this.removeTiddler_orig_renameTags(title);
		}

	},

	init: function() {
		merge(TiddlyWiki.prototype,this.storeMethods);
	}
}

config.renameTags.init();

//}}}

</pre>
</div>
<div title="Revisions Plugin" modifier="YBA" created="200612241949" modified="200809220810" tags="systemConfig excludeSearch excludeLists" server.type="file" server.host="bradleymeck.tiddlyspot.com" server.page.revision="200612250553" changecount="1">
<pre>/***
!Revisions Plugin
!!!About
|Author : Bradley Meck|
|Date : Dec 24, 2006|

This plugin allows people to save multiple revisions of tiddlers and view the changes of tiddlers over time with the ability to restore previous versions of a tiddler.
***/
//{{{
config.macros.revisionsPlugin = { };
config.macros.revisionsPlugin.addRevision = function( tiddler, newContent ){
	
}
//}}}

</pre>
</div>
<div title="RoadmapPreliminaires" modifier="YBA" created="200810151224" modified="200902030909" tags="doc" changecount="46">
<pre>ab -n 100 -c 10 http://localhost/drupal-5.11/ 
* --tester la perte de perf sous VirtualBox : X3--
* --tester sous Xen : perf équivalente--
* --tester sous KVM-- : 
** YBA U804-&gt;U804 : 18 - 11 - 15
** YBA U804-&gt;KVM512 : 7.5
** CDU Suse-&gt;Xen512 + Xen512-&gt;Xen512 : 8.10
** YBA VBox-&gt;VBox : 6
** CDU Suse-&gt;Suse : 15.14
** CDU Xen512-&gt;Xen512 bipro : 12.96 - 15.18
** YBA U804 KVM-&gt;KVM : 7.57 - 8.44
** YBA U804 KVM-&gt;KVM bipro : 15.83
* --tester Drupal 6.5 et 5.11 avec devel-- :
* tester pgsql vs mysql : http://www.wikivs.com/wiki/MySQL_vs_PostgreSQL + http://wiki.startupcamp.org/wiki/Selecting_an_open_source_database:_mySQL_vs_PostgreSQL
* --tester les différents caches-- :
## --http://drupal.org/project/memcache--
## --http://drupal.org/project/apc--
## --http://drupal.org/project/blockcache--
## http://drupal.org/project/boost
## http://drupal.org/project/cacherouter
* --mettre en place un reverse proxy (squid)-- : 885 (50 paralelle pendant 20 s) sans squid 311 (cache de Drupal activé)
* --mettre en place un pseudocluster (la même que celle de drupal.org)--
** --loadbalancer : mod_proxy-- + heartbeat ou UltraMonkey
** --replication mysql --
** --mysql proxy--
** segmentation
* --tester les différents outils de test--
** --ab--
** --siege + sproxy--
** --jmeter--
* --outils de monitoring--
** --monit-- fait pour les alertes
** --cacti : graph configurable via le web, a revoir pour MySQL--
** --munin-- : très bon, plein de plugins, config manuelle
** --pandora fms--
** piwik, awstats, yslow, mysqlreport, mysqlsla, mytop, sar, vmstat
</pre>
</div>
<div title="Récapitulatif" modifier="YBA" created="200808071435" modified="200809171300" changecount="28">
<pre>&lt;&lt;slider chkObj Objectif &quot;Objectif&quot; &gt;&gt;
&lt;&lt;slider chkFeatures Features &quot;Features&quot;&gt;&gt;
&lt;&lt;slider  chkBacklog [[Backlog de produit]] &quot;Backlog de produit&quot;&gt;&gt;
&lt;&lt;slider  chkBrouillon [[Brouillon]] &quot;Brouillon&quot;&gt;&gt;</pre>
</div>
<div title="SideBarOptions" modifier="YBA" created="200809121206" modified="200809221443" changecount="9">
<pre>&lt;&lt;search&gt;&gt;&lt;&lt;closeAll&gt;&gt;&lt;&lt;newTiddler&gt;&gt;&lt;&lt;newTiddler label:&quot;new User Story&quot; text:{{store.getTiddlerText(&quot;EmptyUserStory&quot;)}} title:&quot;Titre court de UserStory&quot; tag:UserStory&gt;&gt;&lt;&lt;newTiddler label:&quot;new Sprint&quot; tag:sprint title:&quot;Nouveau sprint&quot; text:{{store.getTiddlerText(&quot;EmptySprint&quot;)}}&gt;&gt;&lt;&lt;newTiddler label:&quot;new Task&quot; tag:task text:{{store.getTiddlerText(&quot;EmptyTask&quot;)}}&gt;&gt;&lt;&lt;newTiddler label:&quot;new Backlog&quot; tag:backlog text:{{store.getTiddlerText(&quot;EmptyBacklog&quot;)}}&gt;&gt;&lt;&lt;saveChanges&gt;&gt;&lt;&lt;slider chkSliderOptionsPanel OptionsPanel &quot;options »&quot; &quot;Change TiddlyWiki advanced options&quot;&gt;&gt;</pre>
</div>
<div title="SideBarTabs" modifier="YBA" created="200810280930" changecount="1" creator="YBA">
<pre>&lt;&lt;tabs txtMainTab &quot;Doc&quot; &quot;Doc&quot; TabDoc &quot;Timeline&quot; &quot;Timeline&quot; TabTimeline &quot;All&quot; &quot;All tiddlers&quot; TabAll &quot;Tags&quot; &quot;All tags&quot; TabTags &quot;More&quot; &quot;More lists&quot; TabMore&gt;&gt;</pre>
</div>
<div title="SiteSubtitle" modifier="YBA" created="200807211535" modified="200903241649" changecount="5">
<pre>Drupal Haute Performances</pre>
</div>
<div title="SiteTitle" modifier="YBA" created="200807211535" modified="200903241649" changecount="7">
<pre>VBC</pre>
</div>
<div title="SliceGridPlugin" modifier="YBA" created="200809090810" modified="200809221223" tags="systemConfig excludeSearch excludeLists" changecount="14">
<pre>/***
|Name|SliceGridPlugin|
|Source|http://www.TiddlyTools.com/#SliceGridPlugin|
|Documentation|http://www.TiddlyTools.com/#SliceGridPluginInfo|
|Version|1.0.3|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1.3|
|Type|plugin|
|Requires||
|Overrides||
|Description|Generate a &quot;slice grid&quot; table to get an instant 'birds-eye' overview of your entire document|
Rows are tiddlers, columns are slice names.  If a tiddler has a value defined for a given slice, that grid cell is colored. Move the mouse over a grid cell to view the value for that tiddler slice.  To keep the grid display from getting very wide, the slice names used as column headings are not initially displayed.  ''Click directly above the column to show/hide that heading'', or toggle all column headings at once by clicking the {{{&gt;&gt;&gt;}}} symbol in the upper-left corner of the grid display.
!!!!!Documentation
&gt;see [[SliceGridPluginInfo]]
!!!!!Revisions
&lt;&lt;&lt;
2008.08.15 [1.0.3] add brackets around row headings when wikifying so that non-wikiword tiddler titles are linked
2008.08.13 [1.0.2] wikify row headings (in addition to slice value cells) whenever 'wikify' param is used
|please see [[SliceGridPluginInfo]] for additional revision details|
2007.01.30 [0.0.1] started
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.sliceGrid= {major: 1, minor: 0, revision: 3, date: new Date(2008,8,15)};

config.macros.sliceGrid= {
	sizeSliceName: &quot;TiddlerSize&quot;, // shadow slice used for retrieving and showing tiddler size as a grid column
	noSlicesMsg: &quot;\n|~SliceGrid: there are no slices to display|\n&quot;,
	handler:
	function(place,macroName,params) {
		this.slicesRE=/(?:^\|\s*[\'\/]*~?(\w+)\:?[\'\/]*\s*\|\s*(.*?)\s*\|$)/gm;
		this.wikify=false; // wiki-syntax in slices
		this.edit=false; // 'click-to-edit' feature
		this.verbose=false; // debugging/performance feedback messages
		this.alltiddlers=false; // rows for all tiddlers (even if no slices)
		this.inline=false; // slice value in cell (instead of mouseover)
		this.headers=false; // column headers
		this.cliplength=0; // limit inline display text length (0=no limit)
		this.slices=[]; // slices to show (columns)
		var tablewidth=&quot;auto&quot;; // default CSS for table width
		var p=params.shift();
		while (p) {
			if (p.substr(0,6).toUpperCase()==&quot;WIDTH:&quot;)
				tablewidth=p.substr(6);
			else if (p.substr(0,5).toUpperCase()==&quot;TAGS:&quot;) {
				if (p.substr(5,1)==&quot;@&quot;) { // get tags from tiddler
					var tid=store.getTiddler(p.substr(6));
					if (p.substr(6)==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tid=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tid) var tags=tid.tags;
				}
				else if (p.substr(5,1)==&quot;+&quot;) { // get tags from tiddler contents
					var tid=store.getTiddler(p.substr(6));
					if (p.substr(6)==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tid=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tid)
						{ var tags=tid.text.readBracketedList(); }
				}
                                else if (p.substr(5,1)==&quot;=&quot;) { // get tags from value
                                       var tags=p.substr(6).readMacroParams();
                                }
				else var tags=p.substr(5).readBracketedList();
			}
			else if (p.substr(0,7).toUpperCase()==&quot;SLICES:&quot;) {
				if (p.substr(7,1)==&quot;@&quot;) { // get slices from tiddler
					var tid=p.substr(8);
					var tiddler=store.getTiddler(tid);
					if (tid==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tiddler=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tiddler) {
						var slices=tiddler.getSlices(tiddler.title); for (var s in slices) this.slices.push(s);
					}
				}
				else if (p.substr(7,1)==&quot;+&quot;) { // get slices from tiddler contents
					var tid=p.substr(8);
					var tiddler=store.getTiddler(tid);
					if (tid==&quot;here&quot;) {
						var here=story.findContainingTiddler(place);
						if (here) var tiddler=store.getTiddler(here.getAttribute(&quot;tiddler&quot;));
					}
					if (tiddler)
						{ this.slices=tiddler.text.readBracketedList(); }
				}
				else this.slices=p.substr(7).readBracketedList();
			}
			else switch (p.toUpperCase()) {
				case &quot;EDIT&quot;:
					this.edit=true &amp;&amp; !readOnly; break; // no editing allowed if readOnly mode
				case &quot;WIKIFY&quot;:
					this.wikify=true; break;
				case &quot;VERBOSE&quot;:
					this.verbose=true; break;
				case &quot;ALL&quot;:
					this.alltiddlers=true; break;
				case &quot;INLINE&quot;:
					this.inline=true; this.headers=true; break; // default to show headers with inline grid
				default:
					if (!isNaN(p)) { this.cliplength=p; break; }
					else displayMessage(&quot;unrecognized parameter: &quot;+p);
			}
			p=params.shift();
		}
		// get rows and columns
		var rows=[]; var cols=[]; var total=0;
		var tiddlers=store.getTiddlers(&quot;title&quot;,&quot;excludeLists&quot;); // get all tiddlers (except hidden ones)
		if (tags &amp;&amp; tags.length) for (t=0;t&lt;tiddlers.length;t++) // filter out tiddlers with no matching tags
			if (!tiddlers[t].tags.containsAll(tags)) { tiddlers.splice(t,1); t--; } // remove non-matching tiddler -- YBA 22.09.08 patch to get a AND operation
		for (i=0; i&lt;tiddlers.length; i++) {
			var slices=this.getSlices(tiddlers[i].title);
			var count=0; for (var s in slices) { cols.pushUnique(s); count++; total++;}
			if (count || this.alltiddlers) rows.push(tiddlers[i].title); // only show rows for tiddlers with slices
		}
		if (!cols.length) { wikify(this.noSlicesMsg,place); return; }
		if (this.verbose) displayMessage(&quot;Found %0 slices using %1 slice names in %2 tiddlers&quot;.format([total, cols.length,rows.length]));

		// get optional list of slices to show
		if (this.slices.length) var cols=this.slices;

		// generate HTML table
		var out=&quot;&quot;;
		out+=&quot;&lt;html&gt;&lt;table cellpadding='0' cellspacing='0' style='border:0;border-collapse:collapse;width:&quot;+tablewidth+&quot;'&gt;&quot;;
		// column headings
		out+=&quot;&lt;tr style='border:0;'&gt;&lt;td style='text-align:right;border:0'&gt;&quot;;
		out+=&quot;&lt;a href='javascript:;' style='font-size:80%;'&quot;;
		out+=&quot;	title='show all column headings'&quot;;
		out+=&quot;	onclick='return config.macros.sliceGrid.toggleAllColumns(this,event,&quot;+(this.headers?&quot;true&quot;:&quot;false&quot;)+&quot;)'&gt;&quot;;
		out+=this.headers?&quot;&amp;lt;&amp;lt;&amp;lt;&quot;:&quot;&amp;gt;&amp;gt;&amp;gt;&quot;;
		out+=&quot;&lt;/a&gt;&quot;;
		out+=&quot;&lt;/td&gt;&quot;;
		for (var i=0;i&lt;cols.length;i++) {
			out+=&quot;&lt;td style='text-align:center;cursor:pointer;border:0;padding-left:2px;padding-right:2px;' &quot;;
			out+=&quot;	title='show/hide column heading' &quot;;
			out+=&quot;	onclick='return config.macros.sliceGrid.toggleColumn(this,event)'&gt;&quot;;
			out+=&quot;&lt;span style='display:&quot;+(this.headers?&quot;block&quot;:&quot;none&quot;)+&quot;'&gt;&quot;+cols[i]+&quot;&lt;/span&gt;&quot;;
			out+=&quot;&lt;/td&gt;&quot;;
		}
		out+=&quot;&lt;/tr&gt;&quot;;
		for (var i=0;i&lt;rows.length;i++) {
			var tiddlersrc=rows[i].replace(/&quot;/g,&quot;&amp;#x22;&quot;);
			out +=&quot;&lt;tr style='border:0'&gt;&quot;;
			// row heading
			out +=&quot;&lt;td style='text-align:right;border:0;padding-right:2px;white-space:nowrap;&quot;+(this.inline?'width:1%':'')+&quot;;'&gt;&quot;;
			out +=&quot;&lt;a href='javascript:;' tid=\&quot;&quot;+tiddlersrc+&quot;\&quot; class='slicegrid_heading' &quot;;
			out +=&quot;		onclick='story.displayTiddler(this,this.getAttribute(\&quot;tid\&quot;));return false'&gt;&quot;+tiddlersrc+&quot;&lt;/a&gt;&quot;;
			out +=&quot;&lt;/td&gt;&quot;;
			var slices=this.getSlices(rows[i]);
			for (var j=0;j&lt;cols.length;j++) {
				var val=slices[cols[j]]; if (!val) val=&quot;&quot;;
				var bgcolor=val.length?&quot;#999&quot;:&quot;transparent&quot;;
				var content=&quot;&amp;nbsp;&quot;;
				if (val.length &amp;&amp; this.inline) {
					content=val.htmlEncode();
					if (this.cliplength) content=val.substr(0,this.cliplength)+(val.length&gt;this.cliplength?&quot;...&quot;:&quot;&quot;);
					bgcolor=&quot;transparent&quot;;
				}
				out+=&quot;&lt;td style='background-color:&quot;+bgcolor+&quot;;border:1px solid;&quot;+(!this.inline?'width:1em;':'');
				if (this.inline &amp;&amp; cols[j]==this.sizeSliceName) out+=&quot;text-align:right !important;&quot;; // right align tiddler size pseudo-slice
				out+=&quot;'&quot;; 
				if (this.edit &amp;&amp; !(cols[j]==this.sizeSliceName)) { // add edit-in-place, except for tiddler size pseudo-slice
					var onclick=&quot;return config.macros.sliceGrid.editSlice(this,event,this.getAttribute(\&quot;tid\&quot;),\&quot;&quot;+cols[j]+&quot;\&quot;,&quot;+(this.inline?&quot;true&quot;:&quot;false&quot;)+&quot;)&quot;;
					out+=&quot; tid=\&quot;&quot;+tiddlersrc+&quot;\&quot; onclick='&quot;+onclick+&quot;'&quot;;
				}
				out+=&quot; title=\&quot;&quot;+tiddlersrc+&quot;::&quot;+cols[j]+(!this.inline?(&quot;=&quot;+val):&quot;&quot;)+&quot;\&quot;&quot;;
				out+=&quot;&gt;&lt;span class='slicegrid_content'&gt;&quot;+content+&quot;&lt;/span&gt;&lt;/td&gt;&quot;;
			}
			out+=&quot;&lt;/tr&gt;&quot;;
		}
		out+=&quot;&lt;/table&gt;&quot;;
		out+=&quot;&lt;/html&gt;&quot;;
		var span=createTiddlyElement(place,&quot;span&quot;)
		span.innerHTML=out;
		if (this.wikify) {
			// find all TD's in table and replace span content with wikified elements
			var tds=span.getElementsByTagName(&quot;td&quot;);
			for (var t=0; t&lt;tds.length; t++) {
				var content=tds[t].firstChild;
				if (hasClass(content,&quot;slicegrid_heading&quot;)||hasClass(content,&quot;slicegrid_content&quot;)) {
					var txt=getPlainText(content);
					if (hasClass(content,&quot;slicegrid_heading&quot;)) txt=&quot;[[&quot;+txt+&quot;]]&quot;;
					removeChildren(content);
					wikify(txt,content);
				}
			}
		}
	},
	toggleAllColumns:
	function(here,event,defOpen) {
		if (here.expanded==undefined) here.expanded=defOpen;
		var ex=here.expanded=!here.expanded; 
		here.innerHTML=ex?&quot;&amp;lt;&amp;lt;&amp;lt;&quot;:&quot;&amp;gt;&amp;gt;&amp;gt;&quot;;
		here.title=ex?'hide all column headings':'show all column headings';
		var cells=here.parentNode.parentNode.getElementsByTagName(&quot;td&quot;);
		for (i=1; i&lt;cells.length; i++) cells[i].firstChild.style.display=ex?&quot;inline&quot;:&quot;none&quot;;
		event.cancelBubble = true;
		if (event.stopPropagation) event.stopPropagation();
		return(false);
	},
	toggleColumn:
	function(here,event) {
		here.firstChild.style.display=(here.firstChild.style.display==&quot;none&quot;)?&quot;inline&quot;:&quot;none&quot;;
		event.cancelBubble = true;
		if (event.stopPropagation) event.stopPropagation();
		return(false);
	},
	editSlice:
	function(here,event,tid,slice,inline) {
		// replace prompt box with edit field that is displayed until onBlur
		if (here.editing) return false; // already editing... don't re-init

		var onkeyup='if (event.keyCode==13)'; // SAVE and END
		onkeyup+='	{ config.macros.sliceGrid.setSlice(this.getAttribute(\&quot;tid\&quot;),&quot;'+slice+'&quot;,this.value); this.blur(); }';
		onkeyup+='else if (event.keyCode==27)'; // END without saving
		onkeyup+='	{ this.blur(); }';

		var onblur=&quot;var tid=this.getAttribute(\&quot;tid\&quot;); var slice=\&quot;&quot;+slice+&quot;\&quot;; var currval=config.macros.sliceGrid.getSlice(tid,slice); &quot;;
		onblur+=&quot;if (this.value!=currval &amp;&amp; confirm(tid+\&quot;::\&quot;+slice+\&quot; has changed... save value?\&quot;))&quot;;
		onblur+=&quot;	{ config.macros.sliceGrid.setSlice(tid,slice,this.value); currval=this.value; }&quot;;
		onblur+=&quot;var target=this.parentNode;&quot;;
		onblur+=&quot;target.editing=false;&quot;;
		onblur+=&quot;target.style.backgroundColor=target.getAttribute(\&quot;savedColor\&quot;);&quot;;
		onblur+=&quot;target.style.padding=target.getAttribute(\&quot;savedPadding\&quot;);&quot;;
		onblur+=&quot;target.style.width=target.getAttribute(\&quot;savedWidth\&quot;);&quot;;
		onblur+=&quot;if (config.macros.sliceGrid.wikify) { removeChildren(target); wikify(currval,target); }&quot;;
		onblur+=&quot;else target.innerHTML=&quot;+(inline?'currval':'\&quot;&amp;nbsp;\&quot;')+&quot;;&quot;;

		var style=&quot;font-size:1em;font-family:inherit;width:95%;margin:0px;padding:1px 0px 1px 3px;border:0;&quot;;
		var title=tid+&quot;::&quot;+slice+&quot; (ENTER=submit, ESC=cancel)&quot;;

		here.setAttribute(&quot;savedWidth&quot;,here.style.width); if (!inline) here.style.width=&quot;99%&quot;;
		here.setAttribute(&quot;savedPadding&quot;,here.style.padding); here.style.padding=&quot;0px&quot;; 
		here.setAttribute(&quot;savedColor&quot;,here.style.backgroundColor); here.style.backgroundColor=&quot;#fff&quot;;
		here.innerHTML=&quot;&lt;input type='text' tid=\&quot;&quot;+tid.replace(/&quot;/g,&quot;&amp;#x22;&quot;)+&quot;\&quot; onkeyup='&quot;+onkeyup+&quot;' onblur='&quot;+onblur+&quot;' style='&quot;+style+&quot;' title='&quot;+title+&quot;'&gt;&quot;;
		here.firstChild.value=config.macros.sliceGrid.getSlice(tid,slice); // avoids conflicts with nested quoting in HTML
		here.firstChild.focus();
		here.firstChild.select();
		here.editing=true;

		event.cancelBubble = true; if (event.stopPropagation) event.stopPropagation(); return(false);
	},
	getSlice:
	function(tid,slice) {
		var slices=config.macros.sliceGrid.getSlices(tid);
		return slices[slice]?slices[slice]:&quot;&quot;;
	},
	getSlices:
	function(tid) {
		var slices = {};
		var text = store.getTiddlerText(tid,&quot;&quot;);
		slices[this.sizeSliceName]=text.length.toString();  // 'shadow slice' for displaying tiddler size
		this.slicesRE.lastIndex = 0;
		do {
			var m = this.slicesRE.exec(text);
			if (m) { if (m[1]) slices[m[1]] = m[2]; else slices[m[3]] = m[4]; }
		} while(m);
		return slices;
	},
	setSlice:
	function(tid,slice,val) {
		var oldval=this.getSlice(tid,slice);
		if (val==oldval) return false; // value is unchanged... do nothing
		// find slice within tiddler text
		var tiddler=store.getTiddler(tid);
		this.slicesRE.lastIndex = 0;
		var lastIndex=0;
		do {
			var m=this.slicesRE.exec(tiddler.text);
			if (m &amp;&amp; (m[1]==slice||m[3]==slice)) break;
			if (m) lastIndex=this.slicesRE.lastIndex;
		} while(m);
		if (m) { // if matching slice was found
			if (oldval.length) { // replace old value with new value
				var pos=RegExp.lastMatch.indexOf(oldval);
				var newSlice=RegExp.lastMatch.substr(0,pos)+val+RegExp.lastMatch.substr(pos+oldval.length);
			} else // insert new value into empty slice
				var newSlice=RegExp.lastMatch.substr(0,RegExp.lastMatch.length-1)+val+&quot;|&quot;;
			var newText=RegExp.leftContext+newSlice+RegExp.rightContext;
		} else { // create new slice following last slice (or at start of tiddler if no slices)
			var newSlice=&quot;|&quot;+slice+&quot;|&quot;+val+&quot;|\n&quot;;
			var newText=newSlice+tiddler.text
			if (lastIndex) var newText=tiddler.text.substr(0,lastIndex+1)+newSlice+tiddler.text.substr(lastIndex+1);
		}
		// write tiddler contents
		var user=config.options.txtUserName; var mod=new Date();
		if (config.options.chkForceMinorUpdate)
			{ var user=tiddler.modifier; var mod=tiddler.modified; }
		store.saveTiddler(tid,tid,newText,user,mod,tiddler.tags,tiddler.fields);
		displayMessage(tid+&quot;::&quot;+slice+&quot; has been &quot;+(m?&quot;updated&quot;:&quot;created&quot;));
		return(false);
	}
};
//}}}</pre>
</div>
<div title="SliceGridPluginInfo" modifier="YBA" created="200809221212" modified="200809221218" tags="excludeLists" changecount="2">
<pre>/***
|Name|SliceGridPluginInfo|
|Source|http://www.TiddlyTools.com/#SliceGridPlugin|
|Documentation|http://www.TiddlyTools.com/#SliceGridPluginInfo|
|Version|1.0.3|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1.3|
|Type|documentation|
|Requires||
|Overrides||
|Description|documentation for SliceGridPlugin|
Rows are tiddlers, columns are slice names.  If a tiddler has a value defined for a given slice, that grid cell is colored. Move the mouse over a grid cell to view the value for that tiddler slice.  To keep the grid display from getting very wide, the slice names used as column headings are not initially displayed.  ''Click directly above the column to show/hide that heading'', or toggle all column headings at once by clicking the {{{&gt;&gt;&gt;}}} symbol in the upper-left corner of the grid display.
!!!!!Usage
&lt;&lt;&lt;
{{{&lt;&lt;sliceGrid [[tags:tag tag...]] [[slices:slice slice...]]  inline wikify edit all cliplength verbose&gt;&gt;}}}

all parameters are optional, where:
''tags:'' (list)
&gt; display rows only for those tiddlers with at least ONE of the specified tags (space-separated).  default is to show all tiddlers that have at least one slice defined, regardless of tags
&gt; To use a list of tags defined in a separate tiddler, specify ''{{{tags:+TiddlerName}}}''.  To use the same tags that are being used by a given tiddler, use ''{{{tags:@TiddlerName}}}''
''slices:'' (list)
&gt; display columns for the specified slices.  Default is to display all defined slices
&gt; To use a list of slices defined in a separate tiddler, specify ''{{{slices:+TiddlerName}}}''.  To use the same slices that are being used by a given tiddler, use ''{{{slices:@TiddlerName}}}''.  Note: a special &quot;shadow slice&quot;, ''TiddlerSize'', can be used to automatically compute and display the tiddler size in a grid column.  If a tiddler contains a slice named ''TiddlerSize'', then it supercedes the computed value.
''inline'' (keyword)
&gt; display slice value directly in table cell.  Default is to use colored blocks for defined slices, with mouseover 'tooltip' to show value
''wikify'' (keyword)
&gt; parse and format wiki-syntax contained in slice values.  Default is to show slice values &quot;as-is&quot;, without converting embedded formatting.
''edit'' (keyword)
&gt; enable click-to-edit to change slice values.  Default is to display only.
''all'' (keyword)
&gt; display a row for each tiddler, even tiddlers that have NO slices defined.   Default is to only show tiddlers that have slices defined. Note: if tags: param is used to select tiddler by tag, then only tiddlers with matching tags are included.
''cliplength'' (number)
&gt; # of characters to show in table cell when using 'inline' keyword.  Text display is truncated with &quot;...&quot;
''verbose'' (keyword)
&gt; adds messages to report on slice grid processing

Note: uses modified version of slices pattern:
{{{slicesRE = /(?:^\|[\'\/]*~?(\w+)\:?[\'\/]*\|\s*(.*?)\s*\|$)/gm;}}}
* eliminates TONS of spurious slices caused by over-eager &quot;description&quot; format pattern matches
* enforces beginning-of-line and end-of-line sequences.  (Allows slice values to contain |, such as in pretty links)
&lt;&lt;&lt;
!!!!!Examples
&lt;&lt;&lt;
{{{&lt;&lt;sliceGrid inline wikify edit [[slices:Version Description]]&gt;&gt;}}}
+++[click to view example...]...&lt;&lt;sliceGrid inline wikify edit [[slices:Version Description]]&gt;&gt;===

{{{&lt;&lt;sliceGrid edit tags:systemConfig&gt;&gt;}}}
+++[click to view example...]...&lt;&lt;sliceGrid edit tags:systemConfig&gt;&gt;===
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2008.08.15 [1.0.3] add brackets around row headings when wikifying so that non-wikiword tiddler titles are linked
2008.08.13 [1.0.2] wikify row headings (in addition to slice value cells) whenever 'wikify' param is used
2008.05.01 [1.0.1] in editSlice(), corrected fixup for titles containing double-quotes
2007.09.18 [1.0.0] added 'wikify' param and handling to format wiki-syntax contained in slice values
2008.05.01 [1.0.1] in editSlice(), corrected fixup for titles containing double-quotes
2007.09.18 [1.0.0] added 'wikify' param and handling to format wiki-syntax contained in slice values
2007.08.02 [0.9.8] when generating HTML, replace double-quotes in tiddler titles with {{{&amp;#x22;}}} (entity equivalence) to avoid ambiguity with quotes used as HTML attribute syntax delimiters.
2007.07.22 [0.9.7] removed hard-coded 99% 'width bias' and wordwrap from Description cells, default to autosize (equal size for all columns).  Added width:xxxx param to easily stretch table to fit containing space
2007.06.08 [0.9.6] fixed generation of javascript for rows headings and in edit_slice(), so tiddler titles containing single-quote characters will not create syntax errors in the DOM event handlers
2007.04.24 [0.9.5] added 'shadow slice', &quot;TiddlerSize&quot;, so that tiddler size can be retrieved and displayed as a grid column.  Also, fixed click on column headings for show/hide...
2007.04.13 [0.9.4] when getting tiddlers, honor &quot;excludeLists&quot; tag so that 'hidden' tiddlers aren't displayed in the grid
2007.04.08 [0.9.3] corrected setting of cell background color (non-inline grid 'blocks') after edit-in-place adds/clears a slice value
2007.04.07 [0.9.2] support edit in place for non-inline grids (color 'blocks'), and also handle creating new slices in tiddlers where a slice didn't exist previously
2007.04.07 [0.9.1] use locally-defined slicesRE (and getSlice() method) instead of re-defining core slicesRE.  Avoids breaking use of shadow ColorPalette slices.  
2007.04.05 [0.9.0] Added functioning &quot;set slice&quot; handler and &quot;edit-in-place&quot; feature.  Eliminated mouseover popups (use tooltips instead).  Added ''tags:'', ''slices:'' and ''edit'' params.  Lots of code cleanup.
2007.04.04 [0.8.1] in info() and popup() functions, instead of creating popup display, use element tooltip to show mouseover details.   Avoids incorrect positioning of popup if the 'root' element for the popup is in a DIV that has been scrolled.
2007.03.25 [0.8.0] change to BETA status and added support for &quot;tags:tag tag tag tag&quot; filtering param
2007.02.03 [0.0.2] change display of slices from &quot;tiddler[slidename]&quot; to &quot;tiddler:slicename&quot; to match TW slice syntax
2007.01.30 [0.0.1] started (adapted from TagGridPlugin)
&lt;&lt;&lt;</pre>
</div>
<div title="SortListPlugin" modifier="YBA" created="200809161231" modified="200809220802" tags="systemConfig excludeSearch excludeLists" changecount="11">
<pre>/***
|Name|SortListPlugin|
|Version|0.0.3|
|Author|Yoann BABEL &lt;ybabel@ideia.fr&gt; &amp; Jean-Cédric THÉROND &lt;jctherond@ideia.fr&gt;|
|License|[[Creative Commons Attribution-ShareAlike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/]]|
|~CoreVersion|2.4.0|
|Type|plugin|
|Requires|InlineJavascriptPlugin,NestedSlidersPlugin (Optional)|
|Options|##Configuration|
|Overrides||
|Description|Displays to do information from data elements in the calling tiddler.|
!!!!!Usage
Use sorlist in a tiddler, or in a ViewTemplate
All the lines in the tiddler are displayed in a sortable array
all text after ---- is ignored and not included in the sortable list
!!!!!Configuration
&lt;&lt;&lt;
nope
&lt;&lt;&lt;
!!!!!Revisions
&lt;&lt;&lt;
2009.09.18 |0.0.3| add ---- support
2008.09.12 [0.0.2] fields support added
2008.09.09 [0.0.1] initial release
&lt;&lt;&lt;
!!!!!Known Bugs
* none for the moment
!!!!!Code
***/
//{{{
version.extensions.SortList = {major: 0, minor: 0, revision: 2, date: new Date(2008,9,12)};

config.macros.SortList = {
  handler:
  function(place,macroName, params, wikifier) {
    if (!window.story) window.story=window;
    if (!this.checkForExtensions(place, macroName)) {
      return;
    }
    // Check for NestedSlidersPlugin.
    var nSliders = this.checkForNestedSliders(place, macroName);
    var wrapper = createTiddlyElement(place, &quot;span&quot;);
    var title= story.findContainingTiddler(place).id.substr(7);
    var myTiddler = store.getTiddler(title);
    var titleURI = encodeURIComponent(title);
    var text = &quot;&quot;;
    var completeText = &quot;&quot;;
    var p=params.shift();
    var fieldsToInspect = [];
    var splitParamsFieldsRE = new RegExp(&quot;[,]+&quot;, &quot;g&quot;);
    var FTILength = 0;

    while (p) {
	if (p.substr(0,7).toUpperCase()==&quot;FIELDS:&quot; || p.substr(0,1).match(splitParamsFieldsRE) ){
		fieldsToInspect = p.substring(7).split(splitParamsFieldsRE);
	}
	else {
		if (!isNaN(p.toUpperCase())) { this.cliplength=p; break; }
		else displayMessage(&quot;unrecognized parameter: &quot;+p);
	}
	p=params.shift();
    }
    FTILength = fieldsToInspect.length;

    // Collect the number of tasks in this tiddler.
    var dataCount = parseInt(this.getCount(title));

    // Shortcut variable setup for the scripts below.
    var headJs = 'var t = store.getTiddler(title);';
    headJs += 'var count = parseInt(config.macros.SortList.getCount(title)) || 0;';

    // Shortcut to refresh the current tiddler, used below.
    var refreshJs = 'store.setDirty(title, true);';
    refreshJs += 'story.refreshTiddler(title,null,true);';

    // Insert a simple style sheet eliminating the borders around the table.
    text += '&lt;html&gt;&lt;style id=&quot;ToDoPlugin&quot; type=&quot;text/css&quot;&gt;';
    text += '.viewer .noBorder,.viewer .noBorder td,.viewer .noBorder th,.viewer .noBorder tr{border:0} ';
    text += '&lt;/style&gt;&lt;/html&gt;';

    /*
    // Inline script to add a task to the end of the list.
    text += '&lt;script label=&quot;Add task&quot;&gt;';
    text += 'var title=story.findContainingTiddler(place).id.substr(7);';
    text += headJs;
    text += 'config.macros.SortList.TDPWriter(title,count,\'Task\');';
    text += refreshJs;
    text += '&lt;/script&gt;';
    */

    // Global function to swap a task from this tiddler's list with another either above or below it.
    text += '&lt;script&gt;';
    text += 'window.TDPSwapTask = function(title,item, pos) {';
    text += headJs;
    text += 'var i = parseInt(item);';
    text += '  var tmpVal = config.macros.SortList.TDPReader(title,i);';
    text += '  config.macros.SortList.TDPWriter(title,i,config.macros.SortList.TDPReader(title,String(parseInt(i+pos))));';
    text += '  config.macros.SortList.TDPWriter(title,String(parseInt(i+pos)),tmpVal);';
    text += '};';
    text += '&lt;/script&gt;';

    // Global function to delete a task from this tiddler's list.
    text += '&lt;script&gt;';
    text += 'window.TDPDeleteTask = function(title,item) {';
    text += headJs;
    text += 'var oneFewer = String(parseInt(count-1));';
    text += 'var i = parseInt(item);';
    text += 'for (var v=i+1;v&lt;count;v++) TDPSwapTask(title,v,-1);';
    text += 'config.macros.SortList.TDPErase(title,oneFewer);';
    text += refreshJs;
    text += '};';
    text += '&lt;/script&gt;';

    text += &quot;\n|noBorder|k&quot;;
    text += &quot;\n|!|!&amp;nbsp;&quot;;
	for (var i = 0; i &lt; FTILength; i++ ){
		text += &quot;|!&quot;+fieldsToInspect[i];
	}
    text += &quot;|!|!|&quot;;

    for (var i = 0; i &lt; dataCount ; i++) {
      var addDate = &quot;&quot;;
      var cdate = &quot;&quot;;
      var textboxONC = &quot;&quot;;
      var checkboxONC = &quot;&quot;;
      var checkboxCMD = &quot;&quot;;
      var deleteCMD = &quot;&quot;;

      // Setup two commonly used lines in the onclick scripts.
      inlineSetup = 'var title=&amp;#x27;' + title + '&amp;#x27;;';
      refreshTiddler = 'story.refreshTiddler(title,null,true);';

      // Create the onchange script. (&amp;#x27; is a single quote)
      // Assign unique HTML ID to the input element, &quot;tF{TiddlerName}{count}&quot;
      textboxONC = inlineSetup;
      textboxONC += 'config.macros.SortList.TDPWriter(title,' + i + ',';
      textboxONC += 'document.getElementById(&amp;#x27;tF' + titleURI + i + '&amp;#x27;).value;';//.replace(/&amp;#x27;/g,&amp;#x27;\\&amp;#x27;&amp;#x27;));';
      textboxONC += 'store.setDirty(title, true);';

      // Create the delete task table entry.
      deleteCMD = '|&lt;script label=&quot;&amp;#x2326;&quot; title=&quot;delete task&quot;&gt;';
      deleteCMD += 'TDPDeleteTask(&quot;' + title + '&quot;,' + i + ');';
      deleteCMD += '&lt;/script&gt;';

      // Create the text entry box.
      textentryCMD = '|'+this.TDPReader(title,i,FTILength,fieldsToInspect)+' |';//.replace(/'/,'&amp;#x27;')+' |';

        text += &quot;\n&quot;;
	
        // Output the delete task button.
        text += deleteCMD;

        text += textentryCMD;

        // Move selected task up one space.
       if (i&lt;dataCount-1) {
          text += '&lt;script label=&quot;&amp;#x25BC;&quot; title=&quot;move task down&quot;&gt;';
          text += 'var title = \'' + title + '\';';
          text += 'TDPSwapTask(title,' + i + ',+1);';
          text += refreshJs;
          text += '&lt;/script&gt;';
        }
        text += '|';
        // Move selected task down one space.
        if (i&gt;0) {
          text += '&lt;script label=&quot;&amp;#x25B2;&quot; title=&quot;move task up&quot;&gt;';
          text += 'var title = \'' + title + '\';';
          text += 'TDPSwapTask(title,' + i + ',-1);';
          text += refreshJs;
          text += '&lt;/script&gt;';
        }
        text += '|';
    } // For loop

    // Add the text gathered above for completed items, if any.
    text += completeText;

  // YBA 18.09.2008 &quot;----&quot; patch
  var lines = new Array();
  var chunks = new Array();
  chunks = myTiddler.text.split('\n----\n');
  chunks[0] = text;
  text = chunks.join('\n----\n'); 
  // YBA 18.09.2008 &quot;----&quot; patch

    wikify(text, wrapper, null);
    //wikifier.source = &quot;&quot;;
  }
}

// Internal.
//
config.macros.SortList.TDPWriter = function(title,item,value) {
  var t = store.getTiddler(title);
  var i = parseInt(item);
  var lines = new Array();
  var text = &quot;&quot;;
  var chunks = new Array();   // YBA 18.09.2008 &quot;----&quot; patch
  chunks = t.text.split('\n----\n');   
  text = chunks[0];  
  lines = text.split('\n');
  lines[i] = value;
  chunks[0] = lines.join('\n');  
  t.text = chunks.join('\n----\n');    
}

// Internal.
//
config.macros.SortList.TDPReader = function(title,item,FTILength,fieldsToInspect) {
  var t = store.getTiddler(title);
  var i = parseInt(item);
  var Ctiddler;
  var lines = new Array();
  var text = &quot;&quot;;
  var chunks = new Array(); // YBA 18.09.2008 &quot;----&quot; patch
  chunks = t.text.split('\n----\n');
  text = chunks[0];
  lines = text.split('\n');
  text = lines[i];//.replace(/'/g, &quot;&amp;#x27;&quot;).replace(/&quot;/g, &quot;&amp;quot;&quot;);

        // JCT 12.09.2008 parse ref to tiddlers PATCH
	var firstDoubleHook = parseInt(lines[i].indexOf(&quot;[[&quot;,0) + 2);
	var secondDoubleHook = parseInt(lines[i].indexOf(&quot;]]&quot;,firstDoubleHook));	
	var TwoBeforEnd = parseInt(lines[i].length) - 2;

	var trueTitle = lines[i].slice(firstDoubleHook, secondDoubleHook);

	if (store.getTiddler(trueTitle)){
		Ctiddler = store.getTiddler(trueTitle);
		for (var i = 0; i &lt; FTILength; i++ ){
			if (Ctiddler.fields[fieldsToInspect[i]]){
			  text += &quot; | &quot;+Ctiddler.fields[fieldsToInspect[i]];
			}else{ text += &quot; | &quot;;}
		}
	}else{
		for (var j = 0; j &lt; FTILength; j++ ){
			text += &quot;|&quot;;
		}
	}

  return(text);
}

// Internal.
//
config.macros.SortList.getCount = function(title,item,field) {
  var t = store.getTiddler(title);
  var lines = new Array();
  var text = &quot;&quot;;
  text = t.text.split('\n----\n')[0]; // YBA 18.09.2008 &quot;----&quot; patch
  lines = text.split('\n');
  return(lines.length);
}


// Internal
//
config.macros.SortList.TDPErase = function(title,item) {
  var t = store.getTiddler(title);
  var i = parseInt(item);
  var lines = new Array();
  var text = &quot;&quot;;
  var chunks = new Array(); // YBA 18.09.2008 &quot;----&quot; patch
  chunks = t.text.split('\n----\n');
  text = chunks[0];
  lines = text.split('\n');
  lines.splice(i,1);
  chunks[0] = lines.join('\n');
  t.text = chunks.join('\n----\n'); 
}

// Internal.
//
config.macros.SortList.checkForExtensions = function(place,macroName) {
  if (!version.extensions.inlineJavascript) {
    displayMessage(&quot;&lt;&lt;&quot; + macroName + &quot;&gt;&gt; requires the InlineJavascriptPlugin (You can get it from http://www.tiddlytools.com/#InlineJavascriptPlugin)&quot;);
    return false;
  }
  return true;
}

// Internal.
//
config.macros.SortList.checkForNestedSliders = function(place,macroName) {
  if (!version.extensions.nestedSliders) {
    return false;
  }
  return true;
}
//}}}
</pre>
</div>
<div title="SortListViewTemplate" modifier="Jean-Cédric T." created="200809101537" modified="200809191414" tags="customTemplate" changecount="2">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='SortList'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="SparklinePlugin" modifier="FND" created="200804070000" tags="systemConfig excludeLists excludeSearch" server.type="file" server.host="www.tiddlywiki.com/coreplugins.html" server.page.revision="200804070000">
<pre>/***
|''Name''|SparklinePlugin|
|''Description''|provides support for [[sparklines|http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0001OR&amp;topic_id=1]]|
|''Version''|1.0.0|
|''Status''|stable|
|''Source''|http://www.tiddlywiki.com/plugins.html#SparklinePlugin|
|''~CodeRepository:''|http://svn.tiddlywiki.org/Trunk/association/plugins/SparklinePlugin/SparklinePlugin.js |
|''License''|[[BSD open source license]]|
|''~CoreVersion''|2.3.0|
|''Feedback''|[[TiddlyWiki community|http://groups.google.com/group/TiddlyWiki]] |
|''Keywords''|visualization|
!Usage
{{{
&lt;&lt;sparkline numbers&gt;&gt;
}}}
The macro accepts space-separated numeric values as parameter.
!!Examples
Activity on http://www.tiddlywiki.com during the month of April 2005:
{{{&lt;&lt;sparkline 163 218 231 236 232 266 176 249 289 1041 1835 2285 3098 2101 1755 3283 3353 3335 2898 2224 1404 1354 1825 1839 2142 1942 1784 1145 979 1328 1611&gt;&gt;}}}
&lt;&lt;sparkline 163 218 231 236 232 266 176 249 289 1041 1835 2285 3098 2101 1755 3283 3353 3335 2898 2224 1404 1354 1825 1839 2142 1942 1784 1145 979 1328 1611&gt;&gt;
!Code
***/
//{{{
if(!version.extensions.SparklinePlugin) {
version.extensions.SparklinePlugin = {installed:true};

//--
//-- Sparklines
//--

config.macros.sparkline = {};
config.macros.sparkline.handler = function(place,macroName,params)
{
	var data = [];
	var min = 0;
	var max = 0;
	var v;
	for(var t=0; t&lt;params.length; t++) {
		v = parseInt(params[t]);
		if(v &lt; min)
			min = v;
		if(v &gt; max)
			max = v;
		data.push(v);
	}
	if(data.length &lt; 1)
		return;
	var box = createTiddlyElement(place,&quot;span&quot;,null,&quot;sparkline&quot;,String.fromCharCode(160));
	box.title = data.join(&quot;,&quot;);
	var w = box.offsetWidth;
	var h = box.offsetHeight;
	box.style.paddingRight = (data.length * 2 - w) + &quot;px&quot;;
	box.style.position = &quot;relative&quot;;
	for(var d=0; d&lt;data.length; d++) {
		var tick = document.createElement(&quot;img&quot;);
		tick.border = 0;
		tick.className = &quot;sparktick&quot;;
		tick.style.position = &quot;absolute&quot;;
		tick.src = &quot;data:image/gif,GIF89a%01%00%01%00%91%FF%00%FF%FF%FF%00%00%00%C0%C0%C0%00%00%00!%F9%04%01%00%00%02%00%2C%00%00%00%00%01%00%01%00%40%02%02T%01%00%3B&quot;;
		tick.style.left = d*2 + &quot;px&quot;;
		tick.style.width = &quot;2px&quot;;
		v = Math.floor(((data[d] - min)/(max-min)) * h);
		tick.style.top = (h-v) + &quot;px&quot;;
		tick.style.height = v + &quot;px&quot;;
		box.appendChild(tick);
	}
};


}
//}}}</pre>
</div>
<div title="SprintDays" modifier="YBA" created="200809090833" modified="200809171525">
<pre>j0
j1
j2
j3
j4
j5
j6
j7
j8
j9</pre>
</div>
<div title="SprintViewTemplate" modifier="YBA" created="200809121222" modified="200809221330" tags="customTemplate" changecount="59">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;span class='help'&gt;Pour pré-remplir la liste des UserStory, cliqué sur ce bouton. Il vous suffit ensuite de supprimer celles que vous ne voulez pas. Utilisez '----' pour séparer la liste des UserStory du reste du contenu du sprint.
&lt;div class='viewer' macro='PopulateUpdateList want:UserStory'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='SortList'&gt;&lt;/div&gt;
&lt;h1&gt;TaskBoard&lt;/h1&gt;
&lt;div class='viewer' macro='tiddler TaskBoardBySprint with: {{tiddler.title}}'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;h1&gt;Saisie du burdown chart&lt;/h1&gt;
&lt;div class='viewer' macro='sliceGrid edit inline tags:={{tiddler.title}} slices:+SprintDays'&gt;&lt;/div&gt;
&lt;h1&gt;Affichage du burdown chart&lt;/h1&gt;
&lt;div class='viewer' macro='histoGram tags:={{tiddler.title}} width:200 slices:+SprintDays'&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="StyleSheetLayout" modifier="YBA" created="200809170930" changecount="1" creator="YBA">
<pre>/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
	#mainMenu .tiddlyLinkNonExisting,
	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}

.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0em 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler {padding:1em 1em 0em 1em;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}

.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

.sparkline {line-height:1em;}
.sparktick {outline:0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}

/* ============= new div based columns ================= */
.col {
  float: left;
  vertical-align:top;
}

.cols3, .cols2 {
	overflow: auto; /* contains floats. tnx FND */
}

/* IE not doing these right :( */
/* .cols3&amp;gt;br, .col&amp;gt;br { display:none; } */

/* so try this instead. it's annoying and will mean you have trouble 
with brs in non-mgtdlist content. .normal might help */
.cols2 br, .cols3 br  { display:none; }
.mgtdList br, .normal { display:block!important; }
.col .mgtdList { margin-bottom:1.5em; } /* since we're no longer seeing the brs... */

.cols3 .col {
  width: 33%;
}
.cols2 .col {
  width: 49%; /* 50% makes IE put them under each other */
}
.clear {
  clear: left;
}


/*}}}*/</pre>
</div>
<div title="TabDoc" modifier="YBA" created="200810280929" changecount="1">
<pre>&lt;&lt;timeline  better:true onlyTag:doc&gt;&gt;</pre>
</div>
<div title="TabTimeline" modifier="YBA" created="200810280922" modified="200810280929" changecount="5" creator="YBA">
<pre>&lt;&lt;timeline &gt;&gt;</pre>
</div>
<div title="TaggedTemplateTweak" modifier="YBA" created="200809101250" modified="200809220802" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|Name|TaggedTemplateTweak|
|Source|http://www.TiddlyTools.com/#TaggedTemplateTweak|
|Documentation|http://www.TiddlyTools.com/#TaggedTemplateTweakInfo|
|Version|1.4.0|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements &lt;br&gt;and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides|Story.prototype.chooseTemplateForTiddler()|
|Description|use alternative ViewTemplate/EditTemplate for tiddler's tagged with specific tag values|
This tweak extends story.chooseTemplateForTiddler() so that ''whenever a tiddler is marked with a specific tag value, it can be viewed and/or edited using alternatives to the standard tiddler templates.'' 
!!!!!Documentation
&gt;see [[TaggedTemplateTweakInfo]]
!!!!!Revisions
&lt;&lt;&lt;
2008.05.15 [1.4.0] support use of *shadow* tagged templates (e.g., [[DiscussionViewTemplate]] created by [[DiscussionPlugin]])
2008.05.10 [1.3.0] corrected handling for determining core template when using theme with sections
2008.05.01 [1.2.5] added support for tagged templates stored as sections in a theme
| please see [[TaggedTemplateTweakInfo]] for previous revision details |
2007.06.11 [1.0.0] initial release
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.taggedTemplate= {major: 1, minor: 4, revision: 0, date: new Date(2008,5,15)};

Story.prototype.taggedTemplate_chooseTemplateForTiddler = Story.prototype.chooseTemplateForTiddler
Story.prototype.chooseTemplateForTiddler = function(title,template)
{
	// get default template from core
	var template=this.taggedTemplate_chooseTemplateForTiddler.apply(this,arguments);

	// if the tiddler to be rendered doesn't exist yet, just return core result
	var tiddler=store.getTiddler(title); if (!tiddler) return template;

	// split core template into theme prefix and template name
	var theme=&quot;&quot;;
	var parts=template.split(config.textPrimitives.sectionSeparator);
	if (parts[1]) { theme=parts[0]; template=parts[1]; }
	else theme=config.options.txtTheme||&quot;&quot;; // fallback if theme is not specified
	theme+=config.textPrimitives.sectionSeparator;

	// look for template whose prefix matches a tag on this tiddler
	for (i=0; i&lt;tiddler.tags.length; i++) {
		var t=tiddler.tags[i]+template; // add tag prefix to template
		var c=t.substr(0,1).toUpperCase()+t.substr(1); // capitalized for WikiWord title
		if (store.getTiddlerText(theme+t))	{ template=theme+t; break; } // theme##tagTemplate
		if (store.getTiddlerText(theme+c))	{ template=theme+c; break; } // theme##TagTemplate
		if (store.getTiddlerText(t)) 		{ template=t; break; }	     // tagTemplate
		if (store.getTiddlerText(c))		{ template=c; break; }	     // TagTemplate
	}
	return template;
}
//}}}</pre>
</div>
<div title="TaskBoard" modifier="YBA" created="200809170912" modified="200809221332" changecount="15">
<pre>! Global
{{cols2{
{{col{
!A faire
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;ToDo&quot;)' 
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
}}}
{{col{
!En cours
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;InProgress&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
}}}
}}}
{{cols2{
{{col{
!A vérfier
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;ToVerify&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
}}}
{{col{
!Fait
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;Done&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
}}}
}}}
!Non taggé
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; !tiddler.tags.contains(&quot;InProgress&quot;) &amp;&amp; !tiddler.tags.contains(&quot;ToDo&quot;) &amp;&amp; !tiddler.tags.contains(&quot;ToVerify&quot;) &amp;&amp; !tiddler.tags.contains(&quot;Done&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;

----
&lt;&lt;
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;sprint&quot;)'
write '&quot;![[&quot;+tiddler.title+&quot;]]\n&lt;&lt;tiddler TaskBoardBySprint with: &quot;+tiddler.title+&quot;$))\n&quot;'&gt;&gt;</pre>
</div>
<div title="TaskBoardBySprint" modifier="YBA" created="200809221229" modified="200809221330" changecount="6">
<pre>{{cols2{

{{col{

!!A faire
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;$1&quot;) &amp;&amp; tiddler.tags.contains(&quot;ToDo&quot;)' 
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
}}}

{{col{

!!En cours
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;$1&quot;) &amp;&amp; tiddler.tags.contains(&quot;InProgress&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
}}}

}}}

{{cols2{

{{col{

!!A vérfier
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;$1&quot;) &amp;&amp; tiddler.tags.contains(&quot;ToVerify&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;
}}}

{{col{
!!Fait
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;$1&quot;) &amp;&amp; tiddler.tags.contains(&quot;Done&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;

}}}

}}}
!!Non taggé
&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;$1&quot;) &amp;&amp; !tiddler.tags.contains(&quot;InProgress&quot;) &amp;&amp; !tiddler.tags.contains(&quot;ToDo&quot;) &amp;&amp; !tiddler.tags.contains(&quot;ToVerify&quot;) &amp;&amp; !tiddler.tags.contains(&quot;Done&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;</pre>
</div>
<div title="TaskEditTemplate" modifier="YBA" created="200809121226" modified="200809191535" tags="customTemplate" changecount="14">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'&gt;&lt;span style=&quot;padding-right:0.15em;&quot; macro='monkeyTagger sprint source:sprint'&gt;&lt;span style=&quot;padding-right:0.15em;&quot; macro='monkeyTagger sprint source:status'&gt;&lt;span style=&quot;padding-right:0.15em;&quot; macro='monkeyTagger UserStory source:UserStory'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div macro='annotations'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="TaskList" modifier="YBA" created="200809220906" modified="200809220906" changecount="2">
<pre>&lt;&lt;forEachTiddler where 'tiddler.tags.contains(&quot;task&quot;) &amp;&amp; tiddler.tags.contains(&quot;$1&quot;)'
write '&quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;'&gt;&gt;</pre>
</div>
<div title="TaskViewTemplate" modifier="YBA" created="200809191535" modified="200809221156" tags="customTemplate" changecount="7">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;span macro='toggleTag ToDo .'&gt;&lt;/span&gt;
&lt;span macro='toggleTag InProgress .'&gt;&lt;/span&gt;
&lt;span macro='toggleTag ToVerify .'&gt;&lt;/span&gt;
&lt;span macro='toggleTag Done .'&gt;&lt;/span&gt;
&lt;span class='help'&gt;Une tâche doit appartenir (=être taggée) à une UserStory, et à un &lt;b&gt;&lt;font color='red'&gt;sprint&lt;/font&gt;&lt;/b&gt; (pour qu'elle puisse apparaitre dans son &lt;b&gt;burndow chart&lt;/b&gt;. Son état (Done, ToDo, InProgress, ToVerify) détermine dans quelle section du TaskBoard elle va apparaître.&lt;/span&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="TiddlerListMacro" modifier="YBA" created="200809101205" modified="200809220802" tags="systemConfig excludeSearch excludeLists" changecount="2">
<pre>/***
|''Name:''|TiddlerListMacro|
|''Version:''|2.3 (8-Jan-2008)|
|''Source''|http://jackparke.googlepages.com/jtw.html#TiddlerListMacro ([[del.icio.us|http://del.icio.us/post?url=http://jackparke.googlepages.com/jtw.html%23TiddlerListMacro]])|
|''Author:''|[[Jack]]|
|''Type:''|Macro|
|''Documentation:''|[[TiddlerListMacroDocumentation]]|
!Usage
{{{&lt;&lt;tiddlerList parameter1:&quot;value1&quot; parameter2:&quot;value2&quot; ...&gt;&gt;}}}
See TiddlerListMacroDocumentation and TiddlerListMacroExamples
!Code
***/
//{{{
version.extensions.tiddlerList = {major: 2, minor: 3, revision: 0, date: new Date(&quot;Jan 08, 2008&quot;)};
// template = [header, item, separator, group, footer]
config.macros.tiddlerList={
 formats : {list:true, nlist:true, span:true, stack:true, csv:true, table:true},
 templates : {
 list : [ &quot;%0\n&quot;, &quot;* %0\n&quot;, &quot;&quot;, &quot;%group\n&quot;, &quot;%0\n&quot;],
 nlist : [ &quot;%0&quot;, &quot;# %0\n&quot;, &quot;&quot;, &quot;%group\n&quot;, &quot;%0\n&quot;],
 span : [ &quot;%0&quot;, &quot;%0&quot;, &quot; &quot;, &quot;%group&quot;, &quot;%0&quot;],
 stack : [ &quot;%0&quot;, &quot;%0&quot;, &quot;\n&quot;, &quot;%group&quot;, &quot;%0&quot;],
 csv : [ &quot;%0&quot;, &quot;%0&quot;, &quot;, &quot;, &quot;%0&quot;, &quot;%0\n&quot;],
 table : [&quot;|!%0|\n&quot;, &quot;|%0|\n&quot;, &quot;&quot;, &quot;|%group|\n&quot;, &quot;|%0|\n&quot;]
 },
 dateFormat : &quot;DD MMM YYYY&quot;
}

if (typeof gCurrentTiddler == 'undefined')
 var gCurrentTiddler;

config.macros.tiddlerList.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
 // Some globals
 var count=0, groupCount=0, theGroup=&quot;&quot;, lastGroup=&quot;&quot;, firstInGroup = false;
 var currentTiddler = tiddler;
 gCurrentTiddler = tiddler;
 var listWikiText=&quot;&quot;;
 var formats = this.formats;
 
 // SQL-Like parameters
 var parameters = paramString.parseParams(&quot;name&quot;,null,true);
 var pTags = parameters[0][&quot;tags&quot;]?parameters[0][&quot;tags&quot;][0].split(&quot;,&quot;):[];
 var pOrder = parameters[0][&quot;order&quot;]?parameters[0][&quot;order&quot;][0]:&quot;title&quot;;
 var pTop = parameters[0][&quot;top&quot;]?parameters[0][&quot;top&quot;][0]:-1;
 var pText = parameters[0][&quot;text&quot;]?parameters[0][&quot;text&quot;][0]:&quot;&quot;;
 var pTitle = parameters[0][&quot;title&quot;]?parameters[0][&quot;title&quot;][0]:&quot;&quot;;
 var pSearch = parameters[0][&quot;search&quot;]?parameters[0][&quot;search&quot;][0]:&quot;&quot;;
 var pFilter = parameters[0][&quot;filter&quot;]?parameters[0][&quot;filter&quot;][0]:&quot;&quot;;
 var pHeader = parameters[0][&quot;header&quot;]?paramFormat(parameters[0][&quot;header&quot;][0]):&quot;&quot;;
 var pFooter = parameters[0][&quot;footer&quot;]?paramFormat(parameters[0][&quot;footer&quot;][0]):&quot;&quot;;
 var pGroup = parameters[0][&quot;group&quot;]?parameters[0][&quot;group&quot;][0]:&quot;&quot;;
 var pDateFormat = parameters[0][&quot;dateFormat&quot;]?parameters[0][&quot;dateFormat&quot;][0]:this.dateFormat;
 var pCustomParameter = parameters[0][&quot;customParameter&quot;]?parameters[0][&quot;customParameter&quot;][0]:&quot;&quot;;
 var pFormat = parameters[0][&quot;format&quot;]?parameters[0][&quot;format&quot;][0]:&quot;list&quot;;
 pFormat = formats[pFormat]?pFormat:&quot;list&quot;
 
 // Separator
 var pSeparator = parameters[0][&quot;separator&quot;]?paramFormat(parameters[0][&quot;separator&quot;][0]):(parameters[0][&quot;seperator&quot;]?paramFormat(parameters[0][&quot;seperator&quot;][0]):this.templates[pFormat][2])

 // Template for group
 var pGroupTemplate = this.templates[pFormat][3];
 if (parameters[0][&quot;groupTemplate&quot;])
 pGroupTemplate = paramFormat(parameters[0][&quot;groupTemplate&quot;][0])
 pGroupTemplate = pGroupTemplate.replace(&quot;$))&quot;, &quot;&gt;&gt;&quot;)
 
 // Template for group footer
 var pGroupFooterTemplate = &quot;&quot;;
 if (parameters[0][&quot;groupFooterTemplate&quot;])
 pGroupFooterTemplate = paramFormat(parameters[0][&quot;groupFooterTemplate&quot;][0])
 pGroupFooterTemplate = pGroupFooterTemplate.replace(&quot;$))&quot;, &quot;&gt;&gt;&quot;)
 
 // Template for item
 var pItemTemplate = this.templates[pFormat][1];
 if (parameters[0][&quot;itemTemplate&quot;])
 pItemTemplate = paramFormat(parameters[0][&quot;itemTemplate&quot;][0])
 pItemTemplate = pItemTemplate.replace(&quot;$))&quot;, &quot;&gt;&gt;&quot;).replace(&quot;%link&quot;, &quot;%0&quot;).replace(&quot;%item&quot;, &quot;%1&quot;).replace(&quot;%abstract&quot;, &quot;%2&quot;).replace(&quot;%text&quot;, &quot;%3&quot;).replace(&quot;%created&quot;, &quot;%4&quot;).replace(&quot;%modified&quot;, &quot;%5&quot;).replace(&quot;%modifier&quot;, &quot;%6&quot;).replace(&quot;%group&quot;, &quot;%7&quot;).replace(&quot;%title&quot;, &quot;%8&quot;).replace(&quot;%tags&quot;, &quot;%9&quot;).replace(&quot;%nolink&quot;, &quot;%10&quot;).replace(&quot;%custom&quot;, &quot;%11&quot;)
 // Template for footer
 var pFooterTemplate = this.templates[pFormat][4].replace(&quot;%count&quot;, &quot;%1&quot;)

 // Get all tiddlers
 var tiddlers = store.reverseLookup(&quot;tags&quot;,&quot;excludeLists&quot;,false);

 // Sorting
 if(!pOrder)
 pOrder = &quot;title&quot;;
 if (pOrder.match(/^\-/i)) {
 pOrder = pOrder.substr(1)
 var sortDesc = true;
 }
 // Sorting on a standard field
 if (pOrder.match(/(title)|(text)|(modifier)|(modified)|(created)|(tags)/))
  if (sortDesc)
   tiddlers.sort(function (a,b) {if(a[pOrder] == b[pOrder]) return(0); else return (a[pOrder] &gt; b[pOrder]) ? -1 : +1; });
  else
  tiddlers.sort(function (a,b) {if(a[pOrder] == b[pOrder]) return(0); else return (a[pOrder] &lt; b[pOrder]) ? -1 : +1; });
 else
  if (sortDesc)
   tiddlers.sort(function (a,b) {if(a.fields[pOrder] == b.fields[pOrder]) return(0); else return (a.fields[pOrder] &gt; b.fields[pOrder]) ? -1 : +1; });
  else
   tiddlers.sort(function (a,b) {if(a.fields[pOrder] == b.fields[pOrder]) return(0); else return (a.fields[pOrder] &lt; b.fields[pOrder]) ? -1 : +1; });

 // Header
 if (pHeader)
 listWikiText += formatItem(this.templates[pFormat][0], [pHeader], pFormat)
 
 for(var t=0; t&lt;tiddlers.length; t++) {
 tiddler = tiddlers[t];
 if (pText!=&quot;&quot; &amp;&amp; tiddler.text==&quot;&quot;) tiddler.text=store.getValue(tiddler, 'text')
 if (pTop==-1 || count&lt;pTop) {
 if (pText==&quot;&quot; || tiddler.text.match(pText)) {
 if (pTitle==&quot;&quot; || tiddler.title.match(pTitle)) {
 if (pSearch==&quot;&quot; || (tiddler.title.match(pSearch) || tiddler.text.match(pSearch))) {
 if (pFilter==&quot;&quot; || eval(pFilter)) {
 if (pTags.length==0 || compareArrays(tiddler.tags, pTags, &quot;all&quot;)) {
 count++;
 if (tiddler.text==&quot;&quot;) tiddler.text=store.getValue(tiddler, 'text')
 // Grouping
 if (pGroup) {
 theGroup = eval(pGroup);
 if(theGroup != lastGroup) {
 groupCount++;firstInGroup = true;
 if (pGroupFooterTemplate &amp;&amp; groupCount&gt;1)
 listWikiText += pGroupFooterTemplate.replace(&quot;%group&quot;, theGroup)
 listWikiText += pGroupTemplate.replace(&quot;%group&quot;, theGroup)
 lastGroup = theGroup;
 } else
  firstInGroup = false;
 }
 // Separators
 if (count&gt;1 &amp;&amp; !firstInGroup) listWikiText += pSeparator;
 //Plaintext title
 var noLink = tiddler.title.match(config.textPrimitives.wikiLink)?&quot;~&quot; + tiddler.title:tiddler.title;
 // Custom parameter
 if (pCustomParameter)
 var custom=&quot;&quot;;
 try {
 custom = eval(pCustomParameter)
 } catch (e) {}
 // List individual tiddler
 var strItem = formatItem(pItemTemplate,[&quot;[[&quot; + tiddler.title + &quot;]]&quot;,count,tiddler.text.substr(0,300),tiddler.text,tiddler.created.formatString(pDateFormat),tiddler.modified.formatString(pDateFormat),tiddler.modifier,theGroup,tiddler.title,tiddler.tags.join(&quot; &quot;),noLink,custom], pFormat)
 for (var fld in tiddler.fields) strItem = strItem.replace('%field.' + fld, tiddler.fields[fld]);
 listWikiText += strItem
 }
 }
 }
 }
 }
 }
 }
 
 // Last group footer
 if (pGroup &amp;&amp; pGroupFooterTemplate &amp;&amp; count&gt;0)
 listWikiText += pGroupFooterTemplate.replace(&quot;%group&quot;, theGroup)

 // Footer
 if (pFooter) {
 pFooter = pFooter.replace(&quot;%count&quot;, count)
 listWikiText += formatItem(pFooterTemplate, [pFooter], pFormat)
 }
 
 // Render result
 if (!parameters[0][&quot;debug&quot;])
 wikify(listWikiText,place, null, currentTiddler)
 else
 place.innerHTML = &quot;&lt;textarea style=\&quot;width:100%;\&quot; rows=30&gt;&quot; + listWikiText + &quot;&lt;/textarea&gt;&quot;
 
 
 // Local functions
 
 function paramFormat(param) {
 // Allow &quot;\n&quot; in non evalled parameters
 return param.replace(/\\n/g, &quot;\n&quot;);
 }
 
 function formatItem(template, values, format) {
 // Fill template with values (depending on list format)
 if (format.match(/table/) &amp;&amp; values[0].match(/\|/))
 return (&quot;%0\n&quot;).format(values)
 else
 return template.format(values)
 }
 
 function compareArrays(array, values, logic) {
 // Compare items in array with AND(&quot;all&quot;) or OR(&quot;any&quot;) logic
 var matches=0;
 for(var v=0; v&lt;values.length; v++) 
 if(values[v].replace(/^\s+|\s+$/g,&quot;&quot;).match(/^\-/) &amp;&amp; !array.contains(values[v].replace(/^\s+|\s+$/g,&quot;&quot;).substr(1)))
 matches++;
 else if (array.contains(values[v]))
 matches++;
 return ((logic==&quot;all&quot; &amp;&amp; matches==values.length) || (logic!=&quot;all&quot; &amp;&amp; matches&gt;0))
 }
 
}

String.prototype.prettyTrim = function(len,prefix,postfix) {
 var result = this.trim().replace(/\r\n/g,' ').replace(/\n/g,' ');
 if (!prefix) prefix = '';
 if (!postfix) postfix = '';
 if (result.length &gt; len - 3)
 return prefix + result.substr(0,len) + '...' + postfix;
 else if (result.length &gt; 0)
 return prefix + result + postfix;
 else
 return result;
}

//}}}</pre>
</div>
<div title="TiddlerListMacroExamples" modifier="YBA" created="200809101205" changecount="1">
<pre>Examples using the TiddlerListMacro:
!List Formatting
List all tiddlers tagged with &quot;Plugin&quot;
{{{&lt;&lt;tiddlerList tags:Plugin&gt;&gt;}}}
&lt;&lt;tiddlerList tags:Plugin&gt;&gt;
List top 1 plugins which are not templates:
{{{&lt;&lt;tiddlerList tags:&quot;Plugin,-Template&quot; top:1&gt;&gt;}}}
&lt;&lt;tiddlerList tags:&quot;Plugin,-Template&quot; top:1&gt;&gt;
List all tiddlers containing &quot;Plugin&quot; in their title and text (newest first):
{{{&lt;&lt;tiddlerList title:&quot;Plugin&quot; text:&quot;Plugin&quot; order:&quot;-created&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList title:&quot;Plugin&quot; text:&quot;Plugin&quot; order:&quot;-created&quot;&gt;&gt;
List all tiddlers containing &quot;Jack&quot; in their title or text (oldest first):
{{{&lt;&lt;tiddlerList search:&quot;Jack&quot; order:&quot;created&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList search:&quot;Jack&quot; order:&quot;created&quot;&gt;&gt;
List all tiddlers starting with &quot;T&quot;:
{{{&lt;&lt;tiddlerList title:&quot;^T&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList title:&quot;^T&quot;&gt;&gt;
List all tiddlers tagged with the current tiddler's title:
{{{&lt;&lt;tiddlerList tags:{{window.currentTiddler.title}}&gt;&gt;}}}
&lt;&lt;tiddlerList tags:{{window.currentTiddler.title}}&gt;&gt;
!Other Formats
Simple unnumbered list of tiddlers
{{{&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;list&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;list&quot;&gt;&gt;
Numbered list of tiddlers
{{{&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;nlist&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;nlist&quot;&gt;&gt;
Table with header and footer
{{{&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;table&quot; header:&quot;Plugins&quot; footer:&quot;Tiddlers: %count&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;table&quot; header:&quot;Plugins&quot; footer:&quot;Tiddlers: %count&quot;&gt;&gt;
Simple horizontal list:
{{{&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;span&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;span&quot;&gt;&gt;
Comma Separated list
{{{&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;csv&quot; header:&quot;Plugins: [ &quot; footer:&quot; ]&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;3&quot; format:&quot;csv&quot; header:&quot;Plugins: [ &quot; footer:&quot; ]&quot;&gt;&gt;
Custom Separated list
{{{&lt;&lt;tiddlerList top:&quot;10&quot; separator:&quot; - &quot; format:&quot;span&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;10&quot; separator:&quot; - &quot; format:&quot;span&quot;&gt;&gt;
!Grouping
Group tiddlers by first letter
{{{&lt;&lt;tiddlerList top:&quot;7&quot; group:&quot;tiddler.title.substr(0,1)&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;7&quot; group:&quot;tiddler.title.substr(0,1)&quot;&gt;&gt;
!Custom Item Templates
List tiddlers in a custom format (title, date, abstract)
{{{&lt;&lt;tiddlerList tags:&quot;News,-Template&quot; itemTemplate:&quot;* %link (%created)&lt;&lt;br&gt;&gt;%abstract\n&quot; order:&quot;-created&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList tags:&quot;News,-Template&quot; filter:&quot;tiddler.title!=currentTiddler.title&quot; itemTemplate:&quot;%link (%created)\n%abstract&quot; order:&quot;-created&quot;&gt;&gt;
!!!Beware of infinite recursion!!
!Advanced
Tiddlers created today
{{{&lt;&lt;tiddlerList format:table filter:&quot;tiddler.created.formatString('YYYYMMDD')==(new Date()).formatString('YYYYMMDD')&quot; header:{{'Tiddlers created on ' + (new Date()).formatString('DDD, DD MMM YYYY')}}&gt;&gt;}}}
&lt;&lt;tiddlerList format:table filter:&quot;tiddler.created.formatString('YYYYMMDD')==(new Date()).formatString('YYYYMMDD')&quot; header:{{'Tiddlers created on ' + (new Date()).formatString('DDD, DD MMM YYYY')}}&gt;&gt;
Fancy table with grouping on modified date
{{{&lt;&lt;tiddlerList top:&quot;10&quot; header:&quot;|&gt;|!Tiddlers|\n|bgcolor(#ddf):''Title''|bgcolor(#ddf):''Created''|&quot; itemTemplate:&quot;|%0|%4|\n&quot; format:&quot;table&quot; group:&quot;tiddler.created.formatString('DD MMM YYYY')&quot; groupTemplate:&quot;|&gt;|bgcolor(#eef): ''Updated: %group'' |\n&quot; dateFormat:&quot;DDD, DD MM YYYY&quot;&gt;&gt;}}}
&lt;&lt;tiddlerList top:&quot;10&quot; header:&quot;|&gt;|!Tiddlers|\n|bgcolor(#ddf):''Title''|bgcolor(#ddf):''Created''|&quot; itemTemplate:&quot;|%0|%4|\n&quot; format:&quot;table&quot; group:&quot;tiddler.created.formatString('DD MMM YYYY')&quot; groupTemplate:&quot;|&gt;|bgcolor(#eef): ''Updated: %group'' |\n&quot; dateFormat:&quot;DDD, DD MM YYYY&quot;&gt;&gt;</pre>
</div>
<div title="ToDo" modifier="YBA" created="200809151416" tags="status" changecount="1">
<pre></pre>
</div>
<div title="ToVerify" modifier="YBA" created="200809151416" tags="status" changecount="1">
<pre></pre>
</div>
<div title="ToggleTagPlugin" modifier="YBA" created="200809191516" modified="200809220811" tags="systemConfig excludeSearch excludeLists" changecount="4">
<pre>/***
|Name:|ToggleTagPlugin|
|Description:|Makes a checkbox which toggles a tag in a tiddler|
|Version:|3.1.0 ($Rev: 4907 $)|
|Date:|$Date: 2008-05-13 03:15:46 +1000 (Tue, 13 May 2008) $|
|Source:|http://mptw.tiddlyspot.com/#ToggleTagPlugin|
|Author:|Simon Baird &lt;simon.baird@gmail.com&gt;|
|License:|http://mptw.tiddlyspot.com/#TheBSDLicense|
!!Usage
{{{&lt;&lt;toggleTag }}}//{{{TagName TiddlerName LabelText}}}//{{{&gt;&gt;}}}
* TagName - the tag to be toggled, default value &quot;checked&quot;
* TiddlerName - the tiddler to toggle the tag in, default value the current tiddler
* LabelText - the text (gets wikified) to put next to the check box, default value is '{{{[[TagName]]}}}' or '{{{[[TagName]] [[TiddlerName]]}}}'
(If a parameter is '.' then the default will be used)
* TouchMod flag - if non empty then touch the tiddlers mod date. Note, can set config.toggleTagAlwaysTouchModDate to always touch mod date
!!Examples
|Code|Description|Example|h
|{{{&lt;&lt;toggleTag&gt;&gt;}}}|Toggles the default tag (checked) in this tiddler|&lt;&lt;toggleTag&gt;&gt;|
|{{{&lt;&lt;toggleTag TagName&gt;&gt;}}}|Toggles the TagName tag in this tiddler|&lt;&lt;toggleTag TagName&gt;&gt;|
|{{{&lt;&lt;toggleTag TagName TiddlerName&gt;&gt;}}}|Toggles the TagName tag in the TiddlerName tiddler|&lt;&lt;toggleTag TagName TiddlerName&gt;&gt;|
|{{{&lt;&lt;toggleTag TagName TiddlerName 'click me'&gt;&gt;}}}|Same but with custom label|&lt;&lt;toggleTag TagName TiddlerName 'click me'&gt;&gt;|
|{{{&lt;&lt;toggleTag . . 'click me'&gt;&gt;}}}|dot means use default value|&lt;&lt;toggleTag . . 'click me'&gt;&gt;|
!!Notes
* If TiddlerName doesn't exist it will be silently created
* Set label to '-' to specify no label
* See also http://mgtd-alpha.tiddlyspot.com/#ToggleTag2
!!Known issues
* Doesn't smoothly handle the case where you toggle a tag in a tiddler that is current open for editing
* Should convert to use named params
***/
//{{{

if (config.toggleTagAlwaysTouchModDate == undefined) config.toggleTagAlwaysTouchModDate = false;

merge(config.macros,{

	toggleTag: {

		createIfRequired: true,
		shortLabel: &quot;[[%0]]&quot;,
		longLabel: &quot;[[%0]] [[%1]]&quot;,

		handler: function(place,macroName,params,wikifier,paramString,tiddler) {
			var tiddlerTitle = tiddler ? tiddler.title : '';
			var tag   = (params[0] &amp;&amp; params[0] != '.') ? params[0] : &quot;checked&quot;;
			var title = (params[1] &amp;&amp; params[1] != '.') ? params[1] : tiddlerTitle;
			var defaultLabel = (title == tiddlerTitle ? this.shortLabel : this.longLabel);
			var label = (params[2] &amp;&amp; params[2] != '.') ? params[2] : defaultLabel;
			var touchMod = (params[3] &amp;&amp; params[3] != '.') ? params[3] : &quot;&quot;;
			label = (label == '-' ? '' : label); // dash means no label
			var theTiddler = (title == tiddlerTitle ? tiddler : store.getTiddler(title));
			var cb = createTiddlyCheckbox(place, label.format([tag,title]), theTiddler &amp;&amp; theTiddler.isTagged(tag), function(e) {
				if (!store.tiddlerExists(title)) {
					if (config.macros.toggleTag.createIfRequired) {
						var content = store.getTiddlerText(title); // just in case it's a shadow
						store.saveTiddler(title,title,content?content:&quot;&quot;,config.options.txtUserName,new Date(),null);
					}
					else 
						return false;
				}
				if ((touchMod != &quot;&quot; || config.toggleTagAlwaysTouchModDate) &amp;&amp; theTiddler)
						theTiddler.modified = new Date();
				store.setTiddlerTag(title,this.checked,tag);
				return true;
			});
		}
	}
});

//}}}

</pre>
</div>
<div title="ToolbarCommands" modifier="YBA" created="200809151425" modified="200903241644" changecount="2">
<pre>|~ViewToolbar|deleteTiddler refresh closeTiddler closeOthers +editTiddler &gt; fields syncing permalink references jump|
|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|</pre>
</div>
<div title="Tâches pour InTech" modifier="YBA" created="200901051042" modified="200901051051" changecount="2">
<pre>comparer http://drupal.org/project/cacherouter
comparer http://www.ilogan.info/4-alternatives-a-google-analytics/</pre>
</div>
<div title="UploadLog" modifier="YBA" created="200809160846" modified="200809171409">
<pre>| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |
| 16/09/2008 10:46:43 | YBA | [[scrum.html|file:///home/yba/scrum/scrum.html]] | [[store.php|http://192.168.2.23/scrum/store.php]] | . | [[scrumweb.html | http://192.168.2.23/scrum/scrumweb.html]] |  | ok |
| 16/09/2008 10:47:20 | YBA | [[scrum.html|file:///home/yba/scrum/scrum.html]] | [[store.php|http://192.168.2.23/scrum/store.php]] | . | [[scrum.html | http://192.168.2.23/scrum/scrum.html]] |  | ok |
| 16/09/2008 10:49:21 | YBA | [[scrum.html|file:///home/yba/scrum/scrum.html]] | [[store.php|http://192.168.2.23/scrum/store.php]] | . | [[scrum.html | http://192.168.2.23/scrum/scrum.html]] |  | ok |
| 16/09/2008 10:58:38 | YourName | [[scrum.html|http://192.168.2.23/scrum/scrum.html]] | [[store.php|http://192.168.2.23/scrum/store.php]] | . | [[scrum.html | http://192.168.2.23/scrum/scrum.html]] |  |
| 16/09/2008 14:27:34 | Jean-Cédric T. | [[scrum.html|http://192.168.2.23/scrum/scrum.html]] | [[store.php|http://192.168.2.23/scrum/store.php]] | . | [[scrum.html | http://192.168.2.23/scrum/scrum.html]] |  | ok |
| 16/09/2008 14:35:05 | Jean-Cédric T. | [[scrum.html|http://192.168.2.23/scrum/scrum.html]] | [[store.php|http://192.168.2.23/scrum/store.php]] | . | [[scrum.html | http://192.168.2.23/scrum/scrum.html]] |  |
| 17/09/2008 16:09:54 | YBA | [[scrum.html|file:///home/yba/scrum/scrum.html]] | [[store.php|http://192.168.2.23/scrum/store.php]] | . | [[scrum.html | http://192.168.2.23/scrum/scrum.html]] |  | ok |</pre>
</div>
<div title="UploadOptions" modifier="YBA" created="200809160807" modified="200809160811" tags="Upload" changecount="3">
<pre>UploadPlugin uses the following sequence for finding parameters :
#''macro'' parameters
#''Options'' saved in cookies
#''Plugin'' default values
!Options used by UploadPlugin
| Option | Value | Default |
|Upload Username: |&lt;&lt;option txtUploadUserName&gt;&gt;| |
|Upload Password: |&lt;&lt;option pasUploadPassword&gt;&gt;| |
|Url of the UploadService script: |&lt;&lt;option txtUploadStoreUrl urlInput&gt;&gt;| store.php |
|Relative Directory where to store the file: |&lt;&lt;option txtUploadDir urlInput&gt;&gt;| . (the script directory) |
|Filename of the uploaded file: |&lt;&lt;option txtUploadFilename urlInput&gt;&gt;| index.html |
|Directory to backup file on webserver^^(1)^^: |&lt;&lt;option txtUploadBackupDir urlInput&gt;&gt;| //null// (none/no backup) |
|Log in UploadLog |&lt;&lt;option chkUploadLog&gt;&gt; Trace Upload| true |
|Maximum of lines in UploadLog |&lt;&lt;option txtUploadLogMaxLine&gt;&gt;| 10 |

^^(1)^^No backup if Backup Directory is empty, the previous file will be overwritten. Use a '.' to backup in the script directory.

&lt;&lt;upload&gt;&gt; with these options.

!Upload Macro parameters
{{{
&lt;&lt;upload [storeUrl [toFilename [backupDir [uploadDir [username]]]]]&gt;&gt;
 Optional positional parameters can be passed to overwrite UploadOptions. 
}}}

!UploadToFile Macro Macro parameters
{{{
&lt;&lt;uploadTofile [filename [tiddlerTitle]]&gt;&gt;
 tiddlerTitle, filename: if omitted the title of the current tiddler
}}}

&lt;&lt;uploadToFile allowedsites.txt allowedsites.txt&gt;&gt;</pre>
</div>
<div title="UploadPlugin" modifier="YBA" created="200809160802" modified="200809220803" tags="systemConfig Upload excludeSearch excludeLists" changecount="3">
<pre>/***
|''Name:''|UploadPlugin|
|''Description:''|Save to web a TiddlyWiki|
|''Version:''|4.1.0|
|''Date:''|May 5, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#UploadPlugin|
|''Documentation:''|http://tiddlywiki.bidix.info/#UploadPluginDoc|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (#3125)|
|''Requires:''|PasswordOptionPlugin|
***/
//{{{
version.extensions.UploadPlugin = {
	major: 4, minor: 1, revision: 0,
	date: new Date(&quot;May 5, 2007&quot;),
	source: 'http://tiddlywiki.bidix.info/#UploadPlugin',
	author: 'BidiX (BidiX (at) bidix (dot) info',
	coreVersion: '2.2.0 (#3125)'
};

//
// Environment
//

if (!window.bidix) window.bidix = {}; // bidix namespace
bidix.debugMode = false;	// true to activate both in Plugin and UploadService
	
//
// Upload Macro
//

config.macros.upload = {
// default values
	defaultBackupDir: '',	//no backup
	defaultStoreScript: &quot;store.php&quot;,
	defaultToFilename: &quot;index.html&quot;,
	defaultUploadDir: &quot;.&quot;,
	authenticateUser: true	// UploadService Authenticate User
};
	
config.macros.upload.label = {
	promptOption: &quot;Save and Upload this TiddlyWiki with UploadOptions&quot;,
	promptParamMacro: &quot;Save and Upload this TiddlyWiki in %0&quot;,
	saveLabel: &quot;save to web&quot;, 
	saveToDisk: &quot;save to disk&quot;,
	uploadLabel: &quot;upload&quot;	
};

config.macros.upload.messages = {
	noStoreUrl: &quot;No store URL in parmeters or options&quot;,
	usernameOrPasswordMissing: &quot;Username or password missing&quot;
};

config.macros.upload.handler = function(place,macroName,params) {
	if (readOnly)
		return;
	var label;
	if (document.location.toString().substr(0,4) == &quot;http&quot;) 
		label = this.label.saveLabel;
	else
		label = this.label.uploadLabel;
	var prompt;
	if (params[0]) {
		prompt = this.label.promptParamMacro.toString().format([this.destFile(params[0], 
			(params[1] ? params[1]:bidix.basename(window.location.toString())), params[3])]);
	} else {
		prompt = this.label.promptOption;
	}
	createTiddlyButton(place, label, prompt, function() {config.macros.upload.action(params);}, null, null, this.accessKey);
};

config.macros.upload.action = function(params)
{
		// for missing macro parameter set value from options
		var storeUrl = params[0] ? params[0] : config.options.txtUploadStoreUrl;
		var toFilename = params[1] ? params[1] : config.options.txtUploadFilename;
		var backupDir = params[2] ? params[2] : config.options.txtUploadBackupDir;
		var uploadDir = params[3] ? params[3] : config.options.txtUploadDir;
		var username = params[4] ? params[4] : config.options.txtUploadUserName;
		var password = config.options.pasUploadPassword; // for security reason no password as macro parameter	
		// for still missing parameter set default value
		if ((!storeUrl) &amp;&amp; (document.location.toString().substr(0,4) == &quot;http&quot;)) 
			storeUrl = bidix.dirname(document.location.toString())+'/'+config.macros.upload.defaultStoreScript;
		if (storeUrl.substr(0,4) != &quot;http&quot;)
			storeUrl = bidix.dirname(document.location.toString()) +'/'+ storeUrl;
		if (!toFilename)
			toFilename = bidix.basename(window.location.toString());
		if (!toFilename)
			toFilename = config.macros.upload.defaultToFilename;
		if (!uploadDir)
			uploadDir = config.macros.upload.defaultUploadDir;
		if (!backupDir)
			backupDir = config.macros.upload.defaultBackupDir;
		// report error if still missing
		if (!storeUrl) {
			alert(config.macros.upload.messages.noStoreUrl);
			clearMessage();
			return false;
		}
		if (config.macros.upload.authenticateUser &amp;&amp; (!username || !password)) {
			alert(config.macros.upload.messages.usernameOrPasswordMissing);
			clearMessage();
			return false;
		}
		bidix.upload.uploadChanges(false,null,storeUrl, toFilename, uploadDir, backupDir, username, password); 
		return false; 
};

config.macros.upload.destFile = function(storeUrl, toFilename, uploadDir) 
{
	if (!storeUrl)
		return null;
		var dest = bidix.dirname(storeUrl);
		if (uploadDir &amp;&amp; uploadDir != '.')
			dest = dest + '/' + uploadDir;
		dest = dest + '/' + toFilename;
	return dest;
};

//
// uploadOptions Macro
//

config.macros.uploadOptions = {
	handler: function(place,macroName,params) {
		var wizard = new Wizard();
		wizard.createWizard(place,this.wizardTitle);
		wizard.addStep(this.step1Title,this.step1Html);
		var markList = wizard.getElement(&quot;markList&quot;);
		var listWrapper = document.createElement(&quot;div&quot;);
		markList.parentNode.insertBefore(listWrapper,markList);
		wizard.setValue(&quot;listWrapper&quot;,listWrapper);
		this.refreshOptions(listWrapper,false);
		var uploadCaption;
		if (document.location.toString().substr(0,4) == &quot;http&quot;) 
			uploadCaption = config.macros.upload.label.saveLabel;
		else
			uploadCaption = config.macros.upload.label.uploadLabel;
		
		wizard.setButtons([
				{caption: uploadCaption, tooltip: config.macros.upload.label.promptOption, 
					onClick: config.macros.upload.action},
				{caption: this.cancelButton, tooltip: this.cancelButtonPrompt, onClick: this.onCancel}
				
			]);
	},
	refreshOptions: function(listWrapper) {
		var uploadOpts = [
			&quot;txtUploadUserName&quot;,
			&quot;pasUploadPassword&quot;,
			&quot;txtUploadStoreUrl&quot;,
			&quot;txtUploadDir&quot;,
			&quot;txtUploadFilename&quot;,
			&quot;txtUploadBackupDir&quot;,
			&quot;chkUploadLog&quot;,
			&quot;txtUploadLogMaxLine&quot;,
			]
		var opts = [];
		for(i=0; i&lt;uploadOpts.length; i++) {
			var opt = {};
			opts.push()
			opt.option = &quot;&quot;;
			n = uploadOpts[i];
			opt.name = n;
			opt.lowlight = !config.optionsDesc[n];
			opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
			opts.push(opt);
		}
		var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
		for(n=0; n&lt;opts.length; n++) {
			var type = opts[n].name.substr(0,3);
			var h = config.macros.option.types[type];
			if (h &amp;&amp; h.create) {
				h.create(opts[n].colElements['option'],type,opts[n].name,opts[n].name,&quot;no&quot;);
			}
		}
		
	},
	onCancel: function(e)
	{
		backstage.switchTab(null);
		return false;
	},
	
	wizardTitle: &quot;Upload with options&quot;,
	step1Title: &quot;These options are saved in cookies in your browser&quot;,
	step1Html: &quot;&lt;input type='hidden' name='markList'&gt;&lt;/input&gt;&lt;br&gt;&quot;,
	cancelButton: &quot;Cancel&quot;,
	cancelButtonPrompt: &quot;Cancel prompt&quot;,
	listViewTemplate: {
		columns: [
			{name: 'Description', field: 'description', title: &quot;Description&quot;, type: 'WikiText'},
			{name: 'Option', field: 'option', title: &quot;Option&quot;, type: 'String'},
			{name: 'Name', field: 'name', title: &quot;Name&quot;, type: 'String'}
			],
		rowClasses: [
			{className: 'lowlight', field: 'lowlight'} 
			]}
}

//
// upload functions
//

if (!bidix.upload) bidix.upload = {};

if (!bidix.upload.messages) bidix.upload.messages = {
	//from saving
	invalidFileError: &quot;The original file '%0' does not appear to be a valid TiddlyWiki&quot;,
	backupSaved: &quot;Backup saved&quot;,
	backupFailed: &quot;Failed to upload backup file&quot;,
	rssSaved: &quot;RSS feed uploaded&quot;,
	rssFailed: &quot;Failed to upload RSS feed file&quot;,
	emptySaved: &quot;Empty template uploaded&quot;,
	emptyFailed: &quot;Failed to upload empty template file&quot;,
	mainSaved: &quot;Main TiddlyWiki file uploaded&quot;,
	mainFailed: &quot;Failed to upload main TiddlyWiki file. Your changes have not been saved&quot;,
	//specific upload
	loadOriginalHttpPostError: &quot;Can't get original file&quot;,
	aboutToSaveOnHttpPost: 'About to upload on %0 ...',
	storePhpNotFound: &quot;The store script '%0' was not found.&quot;
};

bidix.upload.uploadChanges = function(onlyIfDirty,tiddlers,storeUrl,toFilename,uploadDir,backupDir,username,password)
{
	var callback = function(status,uploadParams,original,url,xhr) {
		if (!status) {
			displayMessage(bidix.upload.messages.loadOriginalHttpPostError);
			return;
		}
		if (bidix.debugMode) 
			alert(original.substr(0,500)+&quot;\n...&quot;);
		// Locate the storeArea div's 
		var posDiv = locateStoreArea(original);
		if((posDiv[0] == -1) || (posDiv[1] == -1)) {
			alert(config.messages.invalidFileError.format([localPath]));
			return;
		}
		bidix.upload.uploadRss(uploadParams,original,posDiv);
	};
	
	if(onlyIfDirty &amp;&amp; !store.isDirty())
		return;
	clearMessage();
	// save on localdisk ?
	if (document.location.toString().substr(0,4) == &quot;file&quot;) {
		var path = document.location.toString();
		var localPath = getLocalPath(path);
		saveChanges();
	}
	// get original
	var uploadParams = Array(storeUrl,toFilename,uploadDir,backupDir,username,password);
	var originalPath = document.location.toString();
	// If url is a directory : add index.html
	if (originalPath.charAt(originalPath.length-1) == &quot;/&quot;)
		originalPath = originalPath + &quot;index.html&quot;;
	var dest = config.macros.upload.destFile(storeUrl,toFilename,uploadDir);
	var log = new bidix.UploadLog();
	log.startUpload(storeUrl, dest, uploadDir,  backupDir);
	displayMessage(bidix.upload.messages.aboutToSaveOnHttpPost.format([dest]));
	if (bidix.debugMode) 
		alert(&quot;about to execute Http - GET on &quot;+originalPath);
	var r = doHttp(&quot;GET&quot;,originalPath,null,null,null,null,callback,uploadParams,null);
	if (typeof r == &quot;string&quot;)
		displayMessage(r);
	return r;
};

bidix.upload.uploadRss = function(uploadParams,original,posDiv) 
{
	var callback = function(status,params,responseText,url,xhr) {
		if(status) {
			var destfile = responseText.substring(responseText.indexOf(&quot;destfile:&quot;)+9,responseText.indexOf(&quot;\n&quot;, responseText.indexOf(&quot;destfile:&quot;)));
			displayMessage(bidix.upload.messages.rssSaved,bidix.dirname(url)+'/'+destfile);
			bidix.upload.uploadMain(params[0],params[1],params[2]);
		} else {
			displayMessage(bidix.upload.messages.rssFailed);			
		}
	};
	// do uploadRss
	if(config.options.chkGenerateAnRssFeed) {
		var rssPath = uploadParams[1].substr(0,uploadParams[1].lastIndexOf(&quot;.&quot;)) + &quot;.xml&quot;;
		var rssUploadParams = Array(uploadParams[0],rssPath,uploadParams[2],'',uploadParams[4],uploadParams[5]);
		bidix.upload.httpUpload(rssUploadParams,convertUnicodeToUTF8(generateRss()),callback,Array(uploadParams,original,posDiv));
	} else {
		bidix.upload.uploadMain(uploadParams,original,posDiv);
	}
};

bidix.upload.uploadMain = function(uploadParams,original,posDiv) 
{
	var callback = function(status,params,responseText,url,xhr) {
		var log = new bidix.UploadLog();
		if(status) {
			// if backupDir specified
			if ((params[3]) &amp;&amp; (responseText.indexOf(&quot;backupfile:&quot;) &gt; -1))  {
				var backupfile = responseText.substring(responseText.indexOf(&quot;backupfile:&quot;)+11,responseText.indexOf(&quot;\n&quot;, responseText.indexOf(&quot;backupfile:&quot;)));
				displayMessage(bidix.upload.messages.backupSaved,bidix.dirname(url)+'/'+backupfile);
			}
			var destfile = responseText.substring(responseText.indexOf(&quot;destfile:&quot;)+9,responseText.indexOf(&quot;\n&quot;, responseText.indexOf(&quot;destfile:&quot;)));
			displayMessage(bidix.upload.messages.mainSaved,bidix.dirname(url)+'/'+destfile);
			store.setDirty(false);
			log.endUpload(&quot;ok&quot;);
		} else {
			alert(bidix.upload.messages.mainFailed);
			displayMessage(bidix.upload.messages.mainFailed);
			log.endUpload(&quot;failed&quot;);			
		}
	};
	// do uploadMain
	var revised = bidix.upload.updateOriginal(original,posDiv);
	bidix.upload.httpUpload(uploadParams,revised,callback,uploadParams);
};

bidix.upload.httpUpload = function(uploadParams,data,callback,params)
{
	var localCallback = function(status,params,responseText,url,xhr) {
		url = (url.indexOf(&quot;nocache=&quot;) &lt; 0 ? url : url.substring(0,url.indexOf(&quot;nocache=&quot;)-1));
		if (xhr.status == httpStatus.NotFound)
			alert(bidix.upload.messages.storePhpNotFound.format([url]));
		if ((bidix.debugMode) || (responseText.indexOf(&quot;Debug mode&quot;) &gt;= 0 )) {
			alert(responseText);
			if (responseText.indexOf(&quot;Debug mode&quot;) &gt;= 0 )
				responseText = responseText.substring(responseText.indexOf(&quot;\n\n&quot;)+2);
		} else if (responseText.charAt(0) != '0') 
			alert(responseText);
		if (responseText.charAt(0) != '0')
			status = null;
		callback(status,params,responseText,url,xhr);
	};
	// do httpUpload
	var boundary = &quot;---------------------------&quot;+&quot;AaB03x&quot;;	
	var uploadFormName = &quot;UploadPlugin&quot;;
	// compose headers data
	var sheader = &quot;&quot;;
	sheader += &quot;--&quot; + boundary + &quot;\r\nContent-disposition: form-data; name=\&quot;&quot;;
	sheader += uploadFormName +&quot;\&quot;\r\n\r\n&quot;;
	sheader += &quot;backupDir=&quot;+uploadParams[3] +
				&quot;;user=&quot; + uploadParams[4] +
				&quot;;password=&quot; + uploadParams[5] +
				&quot;;uploaddir=&quot; + uploadParams[2];
	if (bidix.debugMode)
		sheader += &quot;;debug=1&quot;;
	sheader += &quot;;;\r\n&quot;; 
	sheader += &quot;\r\n&quot; + &quot;--&quot; + boundary + &quot;\r\n&quot;;
	sheader += &quot;Content-disposition: form-data; name=\&quot;userfile\&quot;; filename=\&quot;&quot;+uploadParams[1]+&quot;\&quot;\r\n&quot;;
	sheader += &quot;Content-Type: text/html;charset=UTF-8&quot; + &quot;\r\n&quot;;
	sheader += &quot;Content-Length: &quot; + data.length + &quot;\r\n\r\n&quot;;
	// compose trailer data
	var strailer = new String();
	strailer = &quot;\r\n--&quot; + boundary + &quot;--\r\n&quot;;
	data = sheader + data + strailer;
	if (bidix.debugMode) alert(&quot;about to execute Http - POST on &quot;+uploadParams[0]+&quot;\n with \n&quot;+data.substr(0,500)+ &quot; ... &quot;);
	var r = doHttp(&quot;POST&quot;,uploadParams[0],data,&quot;multipart/form-data; boundary=&quot;+boundary,uploadParams[4],uploadParams[5],localCallback,params,null);
	if (typeof r == &quot;string&quot;)
		displayMessage(r);
	return r;
};

// same as Saving's updateOriginal but without convertUnicodeToUTF8 calls
bidix.upload.updateOriginal = function(original, posDiv)
{
	if (!posDiv)
		posDiv = locateStoreArea(original);
	if((posDiv[0] == -1) || (posDiv[1] == -1)) {
		alert(config.messages.invalidFileError.format([localPath]));
		return;
	}
	var revised = original.substr(0,posDiv[0] + startSaveArea.length) + &quot;\n&quot; +
				store.allTiddlersAsHtml() + &quot;\n&quot; +
				original.substr(posDiv[1]);
	var newSiteTitle = getPageTitle().htmlEncode();
	revised = revised.replaceChunk(&quot;&lt;title&quot;+&quot;&gt;&quot;,&quot;&lt;/title&quot;+&quot;&gt;&quot;,&quot; &quot; + newSiteTitle + &quot; &quot;);
	revised = updateMarkupBlock(revised,&quot;PRE-HEAD&quot;,&quot;MarkupPreHead&quot;);
	revised = updateMarkupBlock(revised,&quot;POST-HEAD&quot;,&quot;MarkupPostHead&quot;);
	revised = updateMarkupBlock(revised,&quot;PRE-BODY&quot;,&quot;MarkupPreBody&quot;);
	revised = updateMarkupBlock(revised,&quot;POST-SCRIPT&quot;,&quot;MarkupPostBody&quot;);
	return revised;
};

//
// UploadLog
// 
// config.options.chkUploadLog :
//		false : no logging
//		true : logging
// config.options.txtUploadLogMaxLine :
//		-1 : no limit
//      0 :  no Log lines but UploadLog is still in place
//		n :  the last n lines are only kept
//		NaN : no limit (-1)

bidix.UploadLog = function() {
	if (!config.options.chkUploadLog) 
		return; // this.tiddler = null
	this.tiddler = store.getTiddler(&quot;UploadLog&quot;);
	if (!this.tiddler) {
		this.tiddler = new Tiddler();
		this.tiddler.title = &quot;UploadLog&quot;;
		this.tiddler.text = &quot;| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |&quot;;
		this.tiddler.created = new Date();
		this.tiddler.modifier = config.options.txtUserName;
		this.tiddler.modified = new Date();
		store.addTiddler(this.tiddler);
	}
	return this;
};

bidix.UploadLog.prototype.addText = function(text) {
	if (!this.tiddler)
		return;
	// retrieve maxLine when we need it
	var maxLine = parseInt(config.options.txtUploadLogMaxLine,10);
	if (isNaN(maxLine))
		maxLine = -1;
	// add text
	if (maxLine != 0) 
		this.tiddler.text = this.tiddler.text + text;
	// Trunck to maxLine
	if (maxLine &gt;= 0) {
		var textArray = this.tiddler.text.split('\n');
		if (textArray.length &gt; maxLine + 1)
			textArray.splice(1,textArray.length-1-maxLine);
			this.tiddler.text = textArray.join('\n');		
	}
	// update tiddler fields
	this.tiddler.modifier = config.options.txtUserName;
	this.tiddler.modified = new Date();
	store.addTiddler(this.tiddler);
	// refresh and notifiy for immediate update
	story.refreshTiddler(this.tiddler.title);
	store.notify(this.tiddler.title, true);
};

bidix.UploadLog.prototype.startUpload = function(storeUrl, toFilename, uploadDir,  backupDir) {
	if (!this.tiddler)
		return;
	var now = new Date();
	var text = &quot;\n| &quot;;
	var filename = bidix.basename(document.location.toString());
	if (!filename) filename = '/';
	text += now.formatString(&quot;0DD/0MM/YYYY 0hh:0mm:0ss&quot;) +&quot; | &quot;;
	text += config.options.txtUserName + &quot; | &quot;;
	text += &quot;[[&quot;+filename+&quot;|&quot;+location + &quot;]] |&quot;;
	text += &quot; [[&quot; + bidix.basename(storeUrl) + &quot;|&quot; + storeUrl + &quot;]] | &quot;;
	text += uploadDir + &quot; | &quot;;
	text += &quot;[[&quot; + bidix.basename(toFilename) + &quot; | &quot; +toFilename + &quot;]] | &quot;;
	text += backupDir + &quot; |&quot;;
	this.addText(text);
};

bidix.UploadLog.prototype.endUpload = function(status) {
	if (!this.tiddler)
		return;
	this.addText(&quot; &quot;+status+&quot; |&quot;);
};

//
// Utilities
// 

bidix.checkPlugin = function(plugin, major, minor, revision) {
	var ext = version.extensions[plugin];
	if (!
		(ext  &amp;&amp; 
			((ext.major &gt; major) || 
			((ext.major == major) &amp;&amp; (ext.minor &gt; minor))  ||
			((ext.major == major) &amp;&amp; (ext.minor == minor) &amp;&amp; (ext.revision &gt;= revision))))) {
			// write error in PluginManager
			if (pluginInfo)
				pluginInfo.log.push(&quot;Requires &quot; + plugin + &quot; &quot; + major + &quot;.&quot; + minor + &quot;.&quot; + revision);
			eval(plugin); // generate an error : &quot;Error: ReferenceError: xxxx is not defined&quot;
	}
};

bidix.dirname = function(filePath) {
	if (!filePath) 
		return;
	var lastpos;
	if ((lastpos = filePath.lastIndexOf(&quot;/&quot;)) != -1) {
		return filePath.substring(0, lastpos);
	} else {
		return filePath.substring(0, filePath.lastIndexOf(&quot;\\&quot;));
	}
};

bidix.basename = function(filePath) {
	if (!filePath) 
		return;
	var lastpos;
	if ((lastpos = filePath.lastIndexOf(&quot;#&quot;)) != -1) 
		filePath = filePath.substring(0, lastpos);
	if ((lastpos = filePath.lastIndexOf(&quot;/&quot;)) != -1) {
		return filePath.substring(lastpos + 1);
	} else
		return filePath.substring(filePath.lastIndexOf(&quot;\\&quot;)+1);
};

bidix.initOption = function(name,value) {
	if (!config.options[name])
		config.options[name] = value;
};

//
// Initializations
//

// require PasswordOptionPlugin 1.0.1 or better
bidix.checkPlugin(&quot;PasswordOptionPlugin&quot;, 1, 0, 1);

// styleSheet
setStylesheet('.txtUploadStoreUrl, .txtUploadBackupDir, .txtUploadDir {width: 22em;}',&quot;uploadPluginStyles&quot;);

//optionsDesc
merge(config.optionsDesc,{
	txtUploadStoreUrl: &quot;Url of the UploadService script (default: store.php)&quot;,
	txtUploadFilename: &quot;Filename of the uploaded file (default: in index.html)&quot;,
	txtUploadDir: &quot;Relative Directory where to store the file (default: . (downloadService directory))&quot;,
	txtUploadBackupDir: &quot;Relative Directory where to backup the file. If empty no backup. (default: ''(empty))&quot;,
	txtUploadUserName: &quot;Upload Username&quot;,
	pasUploadPassword: &quot;Upload Password&quot;,
	chkUploadLog: &quot;do Logging in UploadLog (default: true)&quot;,
	txtUploadLogMaxLine: &quot;Maximum of lines in UploadLog (default: 10)&quot;
});

// Options Initializations
bidix.initOption('txtUploadStoreUrl','');
bidix.initOption('txtUploadFilename','');
bidix.initOption('txtUploadDir','');
bidix.initOption('txtUploadBackupDir','');
bidix.initOption('txtUploadUserName','');
bidix.initOption('pasUploadPassword','');
bidix.initOption('chkUploadLog',true);
bidix.initOption('txtUploadLogMaxLine','10');


// Backstage
merge(config.tasks,{
	uploadOptions: {text: &quot;upload&quot;, tooltip: &quot;Change UploadOptions and Upload&quot;, content: '&lt;&lt;uploadOptions&gt;&gt;'}
});
config.backstageTasks.push(&quot;uploadOptions&quot;);


//}}}

</pre>
</div>
<div title="UploadPluginDoc" modifier="YBA" created="200809160811" tags="Upload" changecount="1">
<pre>//last update: UploadPlugin v 4.1.3//

!Description
UploadPlugin with &lt;&lt;tag UploadService&gt;&gt; extend TiddlyWiki with @@upload@@ and @@save to web@@ commands. 
UploadPlugin uses Username and Password from UploadOptions stored in cookies to authenticate itself to [[store.php]] or [[store.cgi]].
UploadPlugin requires PasswordOptionPlugin.
French translation available as a separate tiddler UploadPluginMsgFR

!!UploadPlugin
*If the TiddlyWiki is viewed @@from local disk@@ :
**{{{&lt;&lt;upload ...&gt;&gt;}}}
***display as '''upload'''
***after saving to disk, upload to a website.
*If the TiddlyWiki is viewed @@from a website@@ :
**{{{&lt;&lt;upload ...&gt;&gt;}}}
***display as '''save to web'''
***save to the same website.
*If GenerateAnRssFeed in AdvancedOptions is set :
**generate the content of the RSSFeed 
**upload the RssFile
**Caution : use the SiteUrl tiddler to specify the right url of the TiddlyWiki in the generated RssFile
*DisplayMessage
*If a backupDir is specified
**and if a file already exists in the uploadDir withe the same fielname, move it in the backup dir and rename it with a timestamp
*if UploadLog is activated every upload action is logged in UploadLog
**only the last maxLines are stored
**if maxLine = -1 no truncation will occured 
hint : if UploadLog is the first tiddler in the TimelineTab, no tiddler has been changed since last upload.

!![[store.php]]
*UserVariables to set :
//{{{
$AUTHENTICATE_USER = true; // true | false
$USERS = array(
 'UserName1'=&gt;'Password1', 
 'UserName2'=&gt;'Password2', 
 'UserName3'=&gt;'Password3'); // set usernames and strong passwords
$DEBUG = false; // true | false
//}}}
*method GET
**display an information page
*method POST
**if $~AUTHENTICATE_USER is ''true''
***presence and value of user and password are checked with $USER and $PASSWORD 
**if toFilename already exists and backDir parameter specified
***rename toFilename to backupDir/toFilename.AAAAMMDD.HHSS.html
**copy temporaryUploadedFile toFilename
**return status

!![[store.cgi]]
*UserVariables to set :
//{{{
CONFIG = {
 :users =&gt; {
 'UserName1'=&gt;'Password1', 
 'UserName2'=&gt;'Password2', 
 'UserName3'=&gt;'Password3')
 },
 :authenticateUser =&gt; true,
 :backupExistingFile =&gt; true,
 :withUploadDir =&gt; true
}
//}}}
*same processing as store.php above

!Usage : 
{{{
&lt;&lt;upload&gt;&gt;
 uses UploadOptions saved in cookies :
 txtUploadUserName: username
 pasUploadPassword : password
 txtUploadStoreUrl : store script
 txtUploadDir : relative path for upload directory
 txtUploadFilename : upload filename
 txtUploadBackupDir : relative path for backup directory

&lt;&lt;upload [storeUrl [toFilename [backupDir [uploadDir [username]]]]]&gt;&gt;
 Optional positional parameters can be passed to overwrite 
 UploadOptions. 
}}}

Suggestion: Install the {{{&lt;&lt;upload ... &gt;&gt;}}} macro in SideBarOptions just below {{{&lt;&lt;saveChanges&gt;&gt;}}} macro.


!User manual
See HowToUpload

!Installation :
*Install the UploadPlugin as usual
*[[Upload]] the [[store.php]] file on your php aware webserver in your TiddlyWiki directory
*Protect your server against malicious upload. Two approaches :
**set $~AUTHENTICATE_USER to true in the [[store.php]] script
***configure $USER and $PASSWORD in the [[store.php]] script on your webserver
***set UploadOptions in conformity with [[store.php]]
**Use server protection :
***for Apache web server ([[for detail see Apache documentation|http://httpd.apache.org/docs/1.3/howto/htaccess.html]]) : 
****configure and upload the [[.htaccess]] [[.passwd]]
***for other web servers see the appropriate documentation
*Configure an upload button, for example in the SideBarOptions
!Suppported Browser
*Firefox and Gecko based browser: tested Ok
*Internet Explorer : tested Ok
*Safari : tested ok on OS X
*Others : Not tested, please report status.

!Revision history
&lt;&lt;tiddler UploadPluginRevisionHistory&gt;&gt;</pre>
</div>
<div title="UploadTiddler" modifier="YBA" created="200809160810" tags="Upload" changecount="1">
<pre>UploadTiddlerPlugin and [[storeTiddler.php]] &quot;upload&quot; or &quot;save to web&quot; a single tiddler by updating, inserting or deleting a single tiddler in a TiddlyWiki file located on the web.

@@Now UploadTiddlerPlugin is able to Upload a Tiddler even if TiddlyWiki is located on a local file.@@

![[UploadTiddlerPlugin]]
*is activated by @@chkUploadTiddler option: &lt;&lt;option chkUploadTiddler&gt;&gt;@@
* to be abe to Upload a Tiddler even if the TiddlyWiki is located on local file@@chkUploadTiddlerFromFile option: &lt;&lt;option chkUploadTiddlerFromFile&gt;&gt;@@
*uses @@txtUploadTiddlerStoreUrl: &lt;&lt;option txtUploadTiddlerStoreUrl&gt;&gt;@@
*override TiddlyWiki.prototype.saveTiddler and deleteTiddler core functions
*uses the same options as UploadPlugin. 
*do an XmlHttpRequest POST to storeTiddler.php only with the corresponding tiddler in storeArea format.
![[storeTiddler.php]]
*Username and password are checked 
*[[storeTiddler.php]] parses TiddlyWiki file
*tiddler is updated, inserted or deleted in the tiddler array
*optionally create and clean backup file
*write updated TiddlyWiki file
!UploadTiddlerPlugin and UploadPlugin
*The paradigm of UploadTiddlerPlugin and [[storeTiddler.php]] is exactly the same as UploadPlugin and [[store.php]]
*No concurrency managed
*Full compatible with UploadPlugin
*[[storeTiddler.php]] can be use to remotly update a TiddlyWiki. storeTiddler.php
*a GET to [[storeTiddler.php]] display a form to upload a tiddler in a TiddlyWiki</pre>
</div>
<div title="UploadTiddlerPlugin" modifier="YBA" created="200809160807" modified="200809220811" tags="systemConfig Upload excludeSearch excludeLists" changecount="2">
<pre>/***
|''Name:''|UploadTiddlerPlugin|
|''Description:''|Upload a tiddler and Update a remote TiddlyWiki |
|''Version:''|1.2.2|
|''Date:''|2008-09-13|
|''Source:''|http://tiddlywiki.bidix.info/#UploadTiddlerPlugin|
|''Usage:''|Uses {{{&lt;&lt;uploadOptions&gt;&gt;}}}&lt;br&gt;with those UploadTiddler Options : &lt;br&gt;chkUploadTiddler: &lt;&lt;option chkUploadTiddler&gt;&gt;&lt;br&gt;txtUploadTiddlerStoreUrl: &lt;&lt;option txtUploadTiddlerStoreUrl&gt;&gt;&lt;br&gt;chkUploadTiddlerFromFile: &lt;&lt;option chkUploadTiddlerFromFile&gt;&gt;|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''[[License]]:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''CoreVersion:''|2.3.0|
***/
//{{{
version.extensions.UploadTiddlerPlugin = {
	major: 1, minor: 2, revision: 2, 
	date: new Date(&quot;2008-09-13&quot;),
	source: 'http://tiddlywiki.bidix.info/#UploadTiddlerPlugin',
	author: 'BidiX (BidiX (at) bidix (dot) info',
	coreVersion: '2.3.0'
};

if (!window.bidix) window.bidix = {}; // bidix namespace
bidix.debugMode = false;
bidix.uploadTiddler = {
	messages: {
		aboutToSaveTiddler: &quot;About to update tiddler '%0'...&quot;,
		aboutToRemotelySaveTiddler: &quot;About to REMOTELY update tiddler '%0'...&quot;,
		storeTiddlerNotFound: &quot;Script store tiddler '%0' not found&quot;,
		tiddlerSaved: &quot;Tiddler '%0' updated in '%1' using '%2' &quot;
	},
	upload: function(title,tiddler,oldTitle) {
		var callback = function(status,params,responseText,url,xhr) {
			if (xhr.status == 404) {
				alert(bidix.uploadTiddler.messages.storeTiddlerNotFound.format([url]));
				return;
			}
			if ((bidix.debugMode) || (responseText.indexOf(&quot;Debug mode&quot;) &gt;= 0 )) {
				alert(responseText);
				if (responseText.indexOf(&quot;Debug mode&quot;) &gt;= 0 )
					responseText = responseText.substring(responseText.indexOf(&quot;\n\n&quot;)+2);
			} else if (responseText.charAt(0) != '0') 
				alert(responseText);
			else 
				displayMessage(bidix.uploadTiddler.messages.tiddlerSaved.format([params[0], params[1], params[2]]));
				store.setDirty(false);
			}

		if ((config.options['chkUploadTiddler']) &amp;&amp; 
				((document.location.toString().substr(0,4) == &quot;http&quot;) || config.options['chkUploadTiddlerFromFile'])) {
			clearMessage();
			if (document.location.toString().substr(0,4) != &quot;http&quot;)
				displayMessage(bidix.uploadTiddler.messages.aboutToRemotelySaveTiddler.format([title]));
			else
				displayMessage(bidix.uploadTiddler.messages.aboutToSaveTiddler.format([title]));
			var ExtTiddler = null;
			var html = null;
			if (tiddler) {
				ExtTiddler = store.getSaver().externalizeTiddler(store,tiddler);
				html = wikifyStatic(tiddler.text,null,tiddler).htmlEncode();
			}
			var form = &quot;title=&quot;+encodeURIComponent(title);
			form = form + &quot;&amp;tiddler=&quot;+(ExtTiddler?encodeURIComponent(ExtTiddler):'');
			form = form + &quot;&amp;html=&quot;+(html?encodeURIComponent(html):'');
			var filename = (config.options['txtUploadFilename']?config.options['txtUploadFilename']:'index.html');
			form = form +&quot;&amp;oldTitle=&quot;+encodeURIComponent(oldTitle);
			form = form +&quot;&amp;fileName=&quot;+encodeURIComponent(filename);
			form = form +&quot;&amp;backupDir=&quot;+encodeURIComponent(config.options['txtUploadBackupDir']);
			form = form +&quot;&amp;user=&quot;+encodeURIComponent(config.options['txtUploadUserName']);
			form = form +&quot;&amp;password=&quot;+encodeURIComponent(config.options['pasUploadPassword']);
			form = form +&quot;&amp;uploadir=&quot;+encodeURIComponent(config.options['txtUploadDir']);
			form = form +&quot;&amp;debug=&quot;+encodeURIComponent(0);
			var storeScript = (config.options.txtUploadTiddlerStoreUrl 
								? config.options.txtUploadTiddlerStoreUrl : 'storeTiddler.php');
			var r = doHttp(&quot;POST&quot;,storeScript,form+&quot;\n&quot;,'application/x-www-form-urlencoded',
				config.options['txtUploadUserName'],config.options['pasUploadPassword'],callback,Array(title,filename, storeScript),null);
		}
	}
}
TiddlyWiki.prototype.saveTiddler_bidix = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(oldTitle,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created) {
	var tiddler = TiddlyWiki.prototype.saveTiddler_bidix.apply(this,arguments);
	var title = (newTitle?newTitle:oldTitle);
	if (oldTitle == title)
		oldTitle = '';
	bidix.uploadTiddler.upload(title, tiddler, oldTitle);
}
TiddlyWiki.prototype.removeTiddler_bidix =TiddlyWiki.prototype.removeTiddler;
TiddlyWiki.prototype.removeTiddler = function(title) {
	TiddlyWiki.prototype.removeTiddler_bidix.apply(this,arguments);
	bidix.uploadTiddler.upload(title, null);
}

//
// Initializations
//

bidix.initOption = function(name,value) {
	if (!config.options[name])
		config.options[name] = value;
};

// styleSheet
setStylesheet('.txtUploadTiddlerStoreUrl {width: 22em;}',&quot;uploadTiddlerPluginStyles&quot;);

//optionsDesc
merge(config.optionsDesc,{
	txtUploadTiddlerStoreUrl: &quot;Url of the UploadTiddlerService script (default: storeTiddler.php)&quot;,
	chkUploadTiddler: &quot;Do per Tiddler upload using txtUploadTiddlerStoreUrl (default: false)&quot;,
	chkUploadTiddlerFromFile: &quot;Upload tiddler even if TiddlyWiki is located on local file (default: false)&quot;
});

// Options Initializations
bidix.initOption('txtUploadTiddlerStoreUrl','');
bidix.initOption('chkUploadTiddler','');
bidix.initOption('chkUploadTiddlerFromFile','');


// add options in backstage UploadOptions
if (config.macros.uploadOptions) {
	if (config.macros.uploadOptions.options) {
		config.macros.uploadOptions.options.push(&quot;txtUploadTiddlerStoreUrl&quot;,&quot;chkUploadTiddler&quot;, &quot;chkUploadTiddlerFromFile&quot;);
	}
}

//}}}

</pre>
</div>
<div title="UploadToHomeMacro" modifier="YBA" created="200809160803" modified="200809220811" tags="systemConfig Upload excludeSearch excludeLists" changecount="3">
<pre>/***
|''Name:''|UploadToHomeMacro|
|''Description:''|Save TiddlyWiki using HomeParameters tiddler|
|''Version:''|0.0.2|
|''Date:''|May 19, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#UploadToHomeMacro|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (#2125)|
|''Requires:''|UploadPlugin|
|''Usage:''|{{{&lt;&lt;uploadToHome [HomeParameters]&gt;&gt;}}}&lt;br&gt;{{{HomeParameters:}}} optional - Tiddler with upload parameters in slices (see HomeParameters).|
***/
//{{{
version.extensions.UploadToHomeMacro = {
	major: 0, minor: 0, revision: 2,
	date: new Date(&quot;May 19, 2007&quot;),
	source: 'http://tiddlywiki.bidix.info/#UploadToHomeMacro',
	author: 'BidiX (BidiX (at) bidix (dot) info',
	coreVersion: '2.2.0 (#3125)'
};

//
// Environment
//

if (!window.bidix) window.bidix = {}; // bidix namespace
bidix.debugMode = false;	// true to activate both in Plugin and UploadService
bidix.checkPlugin(&quot;UploadPlugin&quot;, 4, 1, 0);

//
// uploadUsing Macro
//

config.macros.uploadToHome = {
	handler: function(place,macroName,params) {
		if (readOnly)
			return;
		var label;
		if (document.location.toString().substr(0,4) == &quot;http&quot;) 
			label = config.macros.upload.label.saveLabel;
		else
			label = config.macros.upload.label.uploadLabel;
		var prompt;
		var homeParams = (params[0] ? params[0]:this.messages.homeParamsTiddler);
		if (store.tiddlerExists(homeParams) || store.isShadowTiddler(homeParams)) {
			prompt = this.messages.prompt.toString().format([homeParams]);
		} else {
			throw(this.messages.tiddlerNotFound.toString().format([homeParams]));
		}
		var prompt = this.messages.prompt.toString().format([homeParams]);
		createTiddlyButton(place, label, prompt, function() {config.macros.uploadToHome.action(homeParams);}, null, null, this.accessKey);		
	},
	
	action: function(homeParams) {
		homeParams = (homeParams ? homeParams : config.macros.uploadToHome.messages.homeParamsTiddler);
		if (!store.tiddlerExists(homeParams) &amp;&amp; !store.isShadowTiddler(homeParams)) {
			throw(config.macros.uploadToHome.messages.tiddlerNotFound.toString().format([homeParams]));
		}
		config.macros.upload.action(config.macros.uploadToHome.getParamsFromTiddler(homeParams));
	},
	
	getParamsFromTiddler: function(tiddlerTitle) {
		tiddlerTitle = (tiddlerTitle ? tiddlerTitle:this.messages.homeParamsTiddler);
		if (!store.tiddlerExists(tiddlerTitle) &amp;&amp; !store.isShadowTiddler(tiddlerTitle)) {
			throw(config.macros.uploadToHome.messages.tiddlerNotFound.toString().format([tiddlerTitle]));
		}
		var sliceNames = [
			&quot;UploadStoreUrl&quot;,
			&quot;UploadFilename&quot;,
			&quot;UploadBackupDir&quot;,
			&quot;UploadDir&quot;,
			&quot;UploadUserName&quot;
			//&quot;UploadPassword&quot;, // no password in tiddlers
		];
		var sliceValues = store.getTiddlerSlices(tiddlerTitle,sliceNames);
		var parameters = [];
		for(var i=0; i&lt;sliceNames.length; i++) {
			parameters.push(sliceValues[sliceNames[i]]);
		}
		return parameters;
	},
	
	messages: {
		homeParamsTiddler: &quot;HomeParameters&quot;,
		prompt: &quot;Save and Upload this TiddlyWiki using parameters in  '%0' tiddler&quot;,
		tiddlerNotFound: &quot;Tiddler %0 not found&quot;
	},
	initAtLoad: function () {
		// install Shadowed HomeParameters
		var storeUrl;
		if ((document.location.toString().substr(0,4) == &quot;http&quot;)) 
			storeUrl = bidix.dirname(document.location.toString())+'/'+config.macros.upload.defaultStoreScript;
		else
			storeUrl = config.macros.upload.defaultStoreScript;
		var shadowedHomeParameters = (config.shadowTiddlers['HomeParameters']?config.shadowTiddlers['HomeParameters']:'');
		shadowedHomeParameters += [
			&quot;|UploadUserName:|&quot;+config.options['txtUploadUserName']+&quot;|&quot;,
			&quot;|UploadStoreUrl:|&quot;+storeUrl+&quot;|&quot;,
			&quot;|UploadDir:|.|&quot;,
			&quot;|UploadFilename:|index.html|&quot;,
			&quot;|UploadBackupDir:||&quot;
			].join(&quot;\n&quot;);
		merge(config.shadowTiddlers,{'HomeParameters':	shadowedHomeParameters});
		// install Backstage uploadToHome
		merge(config.tasks,{
			uploadToHome: {text: &quot;uploadToHome&quot;, tooltip: &quot;Upload using '&quot; + this.messages.homeParamsTiddler + &quot;' tiddler&quot;, action: this.action}
		});
		config.backstageTasks.push(&quot;uploadToHome&quot;);
				
	}
};

config.macros.uploadToHome.initAtLoad();

//}}}</pre>
</div>
<div title="UserStory" modifier="YBA" created="200809191255" changecount="1">
<pre>&lt;&lt;deleteAllTagged&gt;&gt;</pre>
</div>
<div title="UserStoryEditTemplate" modifier="Jean-Cédric T." created="200809151418" modified="200809191447" tags="customTemplate" changecount="4">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div macro='annotations'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='formTiddler UserStoryFormTemplate'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="UserStoryFormTemplate" modifier="YBA" created="200809191420" modified="200809220827" tags="customTemplate" changecount="11">
<pre>&lt;html&gt;
&lt;table style=&quot;border:none;&quot;&gt;
&lt;tr  style=&quot;border:none;&quot;&gt;
&lt;td style=&quot;border:none;&quot;&gt;Poids : &lt;/td&gt;&lt;td style=&quot;border:none;&quot;&gt;&lt;select name=poids &gt;&lt;option&gt;1&lt;option&gt;2&lt;option&gt;3&lt;option&gt;5&lt;option&gt;8&lt;option&gt;13&lt;option&gt;21&lt;option&gt;34&lt;option&gt;55&lt;option&gt;89&lt;/select &gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/html&gt;</pre>
</div>
<div title="UserStoryViewTemplate" modifier="YBA" created="200809191416" modified="200809290900" tags="customTemplate" changecount="12">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;

&lt;div class='subtitle'&gt;
	&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; 
	&lt;span macro='view created date'&gt;&lt;/span&gt;)
&lt;/div&gt;
&lt;span class='help'&gt;Une UserStory se décompose en plusieurs tâches. Elle peut &quot;appartenir&quot; a un sprint si elle a été traitée ou en cours de traitement. Si on la tagge &quot;Done&quot;, c'est qu'elle est finie&lt;/span&gt;
&lt;div&gt;Poids: &lt;span macro=&quot;view poids&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;span macro='newHere label:&quot;new Task&quot; text:{{store.getTiddlerText(&quot;EmptyTask&quot;)}} title:&quot;Titre de la tâche&quot; tag:task'&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="VeilleDrupal7" modifier="YBA" created="200810281116" modified="200811240835" tags="doc" changecount="8">
<pre>* http://buytaert.net/starting-to-work-on-drupal-7
* http://core2.drupalfr.org/DrupalCampParis3-StateOfDrupal/080904%20-%20State%20of%20Drupal.pdf from  http://drupalfr.org/node/4820
* http://www.garfieldtech.com/blog/drupal-7-database-plans support de la réplication ??? wow
* http://groups.drupal.org/improvements-core
* http://groups.drupal.org/node/6492/summary pour le wysiwyg, progress
* http://drupal.org/project/usage, http://drupalfr.org/node/5025 sur les modules principaux</pre>
</div>
<div title="VeilleLoadBalancingApache" modifier="YBA" created="200811201703" modified="200811211119" tags="doc" changecount="3">
<pre>* http://www.howtoforge.com/high_availability_loadbalanced_apache_cluster UltraMonkey a tester car mod_proxy semble assez limité en fait ! (d'après les test RapportBenchGlobal du 20.11.08, ceci dit UltraMonkey semble vieux et non maintenant, recherche de solution alternatives
* 21.11.08 http://doc.ubuntu-fr.org/utilisateurs/tokiess ??? ifenslave + bonding, une autre techno plus bas niveau apparement https://help.ubuntu.com/community/UbuntuBonding
</pre>
</div>
<div title="VeilleMachinesVirtuelles" modifier="YBA" created="200810171056" modified="200811181034" tags="doc" changecount="9">
<pre>Xen : n'existe pas sous windows, pour le moment ... http://www.virtualisation-info.com/post/12-xen-support-de-windows-prochainement.htm
KVM : n'existe pas sous windows, mais utilise QEmu qui lui existe http://www.h7.dion.ne.jp/~qemu-win/ mais ca n'a pas l'air très bien supporté
VBox : existe sous windows,mais le plus lent des 3

!13.11.08
http://systeme.developpez.com/tutoriels/virtualisation/livre-blanc-lucas-bonnet/ un très bon livre blanc de comparaison

!18.11.08
http://www.convirt.net/ un manager de machines virtuelles en python qui a l'air sympa</pre>
</div>
<div title="VeilleMySQLCluster" modifier="YBA" created="200811051457" modified="200812011252" tags="doc" changecount="31">
<pre>mot clefs :
* mysql proxy : redirige les requetes, complément de la réplication par ex
* mysql sharding : concept novateur, découpage horizontal des données
* mysql replication : pour la tolérance aux pannes, chaine de réplicateur, accès par programmation au différents noeuds, a voir avec http://drupal.org/project/replication et http://drupal.org/node/147160
* mysql cluster : la réplication est transparente dans une grappe, tolérant aux pannes, haute dispo
* mysql partitionning : découper la base en tranche (groupe de tables)
* mysql segmentation : comme le partitionning

* http://jgrondin.developpez.com/article/MySQL/Replication_MySQL/
* http://linuxfr.org/2007/08/09/22813.html
* http://sangokode.wordpress.com/2007/12/24/mysql-proxy-rw-splitting-replication-mysql/
* http://www.nexen.net/actualites/mysql/17325-introduction_au_proxy_mysql.php
* http://www.nexen.net/actualites/trucs/17423-sharding_:_le_nouveau_must_en_architecture.php explique le concept novateur de sharding, pour le moment, je pense qu'on peut laisser de coter pour une réplication simple
* http://www.nexen.net/actualites/trucs/comment_configurer_un_cluster_mysql.php ca parle des limitations du cluster, sans dire lesquelles
* http://www.commentcamarche.net/forum/affich-6759362-mysql-cluster-haute-disponibilite ils expliquent que Posgres est plus évolué pour les questions de réplication
* un tuto sur le proxy mysql : http://www.oreillynet.com/pub/a/databases/2007/07/12/getting-started-with-mysql-proxy.html
* un tuto sur la segmentation : http://www.johnandcailin.com/blog/john/scaling-drupal-step-four-database-segmentation-using-mysql-proxy
* http://www.johnandcailin.com/blog/john/scaling-drupal-step-two-sticky-load-balancing-apache-modproxy étape de load_balancing

* l'architecture ''skyrock'' : http://www.01net.com/editorial/372709/skyrock-sert-cinq-milliards-de-pages-par-mois/
* un middleware de clustering : http://sequoia.continuent.org/HomePage : supporte uniquement JDBC et ODBC, ce qui n'est pas compatible avec Drupal avant la version 7
* tuto mysql réplication : http://www.netexpertise.eu/fr/mysql/replication-haute-disponibilite.html
* http://dev.mysql.com/tech-resources/articles/proxy-gettingstarted.html cf la partie &quot;simple logging&quot;
* http://www.howtoforge.com/setting-up-a-loadbalanced-mysql-cluster-with-mysql5.1 pour faire du cluster

!MySQLProxy
* http://bugs.mysql.com/bug.php?id=36962 ligne de commande mysqlproxy 0.7
* http://jan.kneschke.de/projects/mysql/mysql-proxy-debug-plugin ca parle du plugin debug de mysql proxy (pas réussi a le lancer)
* http://sangokode.wordpress.com/2007/12/24/mysql-proxy-rw-splitting-replication-mysql/ le tuto de base
* http://www.johnandcailin.com/blog/john/scaling-drupal-step-four-database-segmentation-using-mysql-proxy le tuto du multistep drupal 
* http://dev.mysql.com/tech-resources/articles/proxy-gettingstarted.html le tuto officiel
* http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy-using-admin.html
* http://forums.mysql.com/read.php?146,161563,161563#msg-161563 pour tracer mysql proxy
* http://forums.mysql.com/list.php?146 le forum officiel
* http://forums.mysql.com/read.php?146,198657,198657 un script qui montre l'état de conncetion des serveurs

!MySQLCluster
* http://www.howtoforge.com/loadbalanced_mysql_cluster_debian sous ''debian'' !!!
* ''http://www.howtoforge.org/setting-up-a-loadbalanced-mysql-cluster-with-mysql5.1''
* http://www.ultramonkey.org/ loadbalancing serveur en plus du cluster mysql
* http://dev.mysql.com/tech-resources/articles/falcon-in-depth.html un article sur les moteurs de base de donnée et leur utilisation classique en cluster / réplication
* http://www.heise-online.co.uk/open/New-features-in-MySQL-5-1--/features/112097 un très bon article sur la nouvelle version 5.1 de MySQL, mais surtout sur les défaut des MySQL...</pre>
</div>
<div title="VeillePerfTest" modifier="YBA" created="200810170955" modified="200811191046" tags="doc" changecount="10">
<pre>* webographie PerfTest - http://tag1consulting.com/Load_Testing_Tools_and_Tips
* http://drupal.org/node/79237
* evaluer httperf 
** http://www.pencilcode.com/2008/06/measuring-webserver-performance-httperf/
** http://www.comlore.com/httperf/httperf-quickstart-guide.pdf
* evaluer siege + sproxy (en ligne mais sympa !)
** http://drupal.org/node/48894
** http://www.joedog.org/Sproxy/Manual
* evaluer jmeter
** http://www.johnandcailin.com/blog/john/load-test-your-drupal-application-scalability-apache-jmeter
** http://www.johnandcailin.com/blog/john/load-test-your-drupal-application-scalability-apache-jmeter

* http://www.opensta.org/ un des plus populaires
* http://grinder.sourceforge.net/ parmi les plus populaires aussi, java
* http://www.linux.com/feature/143896 une eval de httperf et d'autres, mention jMeter comme le plus avancé avec un GUI
* http://www.dmoz.org/Computers/Programming/Software_Testing/Load_and_Performance_Testing/
* http://agiletesting.blogspot.com/2005/02/performance-vs-load-vs-stress-testing.html 
* http://www.acme.com/software/http_load/
* http://www.hpl.hp.com/research/linux/httperf/
* http://developer.spikesource.com/frs/?group_id=14 un plugin firefox pour générer des test httperf
* http://selenium-rc.openqa.org/ pour les tests auto avec selenium
* http://agiletesting.blogspot.com/2005/04/http-performance-testing-with-httperf.html utilisation de httperf
* http://www.johnandcailin.com/blog/john/load-test-your-drupal-application-scalability-apache-jmeter un tuto jmeter

! Perf test &amp; Drupal
* http://drupal.org/node/197135 une boite qui utilise WebLoad et fait sa promo
* http://blogs.helion-prime.com/alexshapovalov/2008/07/02/battle-n2-typo3-joomla-and-drupal-cmss-performance-testing.html drupal 6.2 est beaucoup plus rapide que Joomla 1.5 ou Typo3 4.2
* http://mikkel.hoegh.org/blog/2008/drupal_database_performance_mysql_and_postgresql_compared MySQL semble plus rapide que Postgres !!!
* http://www.scribd.com/doc/3991611/Drupal-Performance-and-Scalability les chapitres d'un super bouquin ultra complet sur les perfs drupal, s'en inspirer notament pour les outils choisi notament YSlow pour détecter les parties lentes d'un site
* http://2bits.com/articles/advcache-and-memcached-benchmarks-with-drupal.html un bench de memcached
* http://groups.drupal.org/benchmarking-drupal
* http://groups.drupal.org/high-performance
* http://christian.roy.name/blog/drupal-performance-tuning basique tuning
* ''http://books.tag1consulting.com/scalability''
* http://groups.drupal.org/node/5488 load test group

! 13.11.08
* http://blog.olenepal.org/index.php/archives/300 une doc pour faire du load testing avec AB
* http://www.celebrazio.net/tech/unix/apache_bench.html AB et les &quot;failed request&quot; qui ne sont pas vraiment des erreurs : //In the above example, if the server returns dynamic content, the file size may be different from one request to another. When this happens, Apache bench notes it as &quot;Failed request&quot; for Length being different. We can ignore that.//

!19.11.08
* http://blogs.techrepublic.com.com/howdoi/?p=133 stress test avec mysqlslap</pre>
</div>
<div title="VeilleServerMonitoring" modifier="YBA" created="200810311231" modified="200811251006" tags="doc" changecount="18">
<pre>* http://www.ubuntugeek.com/monitoring-ubuntu-services-using-monit.html
* http://www.howtoforge.com/zenoss_network_monitor_ubuntu
* http://www.howtoforge.com/server-monitoring-with-munin-monit-mandriva2008.0
* http://doc.ubuntu-fr.org/nagios
* http://www.howtogeek.com/howto/ubuntu/monitor-your-website-in-real-time-with-apachetop/
* http://forum.ubuntu-fr.org/viewtopic.php?id=189871 RRDTool
* http://doc.ubuntu-fr.org/rrdtools
* http://doc.ubuntu-fr.org/cacti
* http://en.wikipedia.org/wiki/Pandora_FMS
* http://en.wikipedia.org/wiki/Comparison_of_network_monitoring_systems
* http://www.csquad.org/2006/03/04/munin-une-alternative-simple-a-cacti/
* http://munin.projects.linpro.no/wiki/PluginCat
* http://gorgonite.developpez.com/tutoriels/linux/monit/
* http://gorgonite.developpez.com/tutoriels/systemes/munin/
* 21.11.08 http://www.monitorix.org/features.html a l'air sympa, mais ne fait pas mysql ou apache, seulement le système
* 25.11.08 http://www.howtoforge.com/howtos/monitoring?from=0

Nagios : dans les package, mais j'ai pas réussi a lancer localhost/nagios2 ca plante, ensuite je suis passé par Lilac, mais ca n'a rien donné
Pandora FMS : la machine virtuelle ne tourne pas chez moi
Zenoss : /etc/init.d/zenoss-stack ne marche pas !!!
Zabbix : j'ai commencé a installer, mais pb de paramétrage mem php
vigilo : dependances circulaires des .deb !!!
OpenNMS : plutot Fedora/RedHat (RPM)

! plugins cacti
* http://forums.cacti.net/about9861.html pour apache
* http://code.google.com/p/mysql-cacti-templates/
* http://forums.cacti.net/about11010.html un autre pour mysql
* les meilleurs templates :http://www.generationip.com/documentation/general-documentation/38-Documentation/59-best-cacti-templates-for-application
* tous les templates http://www.debianhelp.co.uk/cactitemplates.htm
* http://blog.nicolargo.com/2008/06/lier-cacti-et-nagios.html nagios + cacti

! Surveiller le réseau
* ifstat ? http://ubuntuforums.org/showthread.php?t=437452
* http://forums.mysql.com/read.php?25,93181,95130#msg-95130 une machine en cluster peut être plus lente que toute seule, un réseau 100Mbps peut être trop lent
* http://www.ubuntugeek.com/bandwidth-monitoring-tools-for-ubuntu-users.html ''surveiller le réseau''
* http://www.cyberciti.biz/faq/debian-ubuntu-install-ntop-network-traffic-monitoring-software/ ntop qui crée des graphiques
* http://jnettop.kubs.info/wiki/ ? a voir
* http://manpages.ubuntu.com/manpages/intrepid/man1/netload.html ? a voir

! Surveiller apache
* apachetop est buggé : https://bugs.launchpad.net/ubuntu/+source/apachetop/+bug/294599
* tcpdump : https://wiki.appnexus.com/display/documentation/How+to+Apache+Stress+Test+With+ab+Tool</pre>
</div>
<div title="VeilleStaging" modifier="YBA" created="200810151219" modified="200812031235" tags="doc" changecount="8">
<pre>!XX/XX/08
http://ecm.ideia.fr/node/30
''http://drupal.org/node/181128'' drupal stagingg solution par france 24, une discussion sur le sujet ''essentiel''
http://drupal.org/project/DAST
http://drupal.org/project/deploy
http://drupal.org/project/node_import
http://drupal.org/project/importexportapi
http://ecm.ideia.fr/admin/settings/profile-generator
http://drupal.org/node/78503 parle du merging de tables mysql mais a priori non relié au problème
http://drupal.org/node/232155 la doc officielle sur cette problématique
http://www.dave-cohen.com/node/1779 un gars qui nous propose sa solution
https://barcamp.pbwiki.com/DrupalCampParisStaging
http://codebaboon.com/method-syncronising-database-changes utilise TOAD, donc peut-être a éviter
http://drupal.org/project/replication
http://groups.drupal.org/node/786
http://drupal.org/node/140430 donne quelques pistes
http://groups.drupal.org/change-management-systems
http://www.spectralcore.com/syncdatabase/index.php un outil pour merge mysql
http://drupal.org/project/journal a voir si ca peut aider
http://www.dave-cohen.com/node/1779 un article qui présente la solution d'un gars, a lire en détail
http://boston2008.drupalcon.org/session/updating-and-upgrading-live-sites  a lire
http://drupal.org/project/autopilot
http://codebaboon.com/method-syncronising-database-changes
''http://www.viddler.com/explore/mikiane/videos/1/171.8/'' la vidéo de france24, comment ils s'y prennent ! a ne pas manquer. 1h

!12/08/08
* http://heyrocker.com/drupal/content/drupalcamp-vancouver-staging-deployment-slides un PDF qui résume bien les différentes options qui débouche sur la solution : http://drupal.org/project/deploy 
* http://drupal.org/node/181128 discussion originale avec la solution de france24 sur les ID pair/impair qui débouche sur ce code : http://codebaboon.com/method-syncronising-database-changes
* une des soluce serait en utilisant devel / macro : http://www.lullabot.com/articles/moving-cck-field-changes-from-dev-to-live

!14.10.08
* http://drupal.org/project/staging 6 ne fonctionne pas tout seul
* http://drupal.org/project/stage 5 ne fonctionne pas avec CCK
* http://drupal.org/project/vhosts 6 j'ai pas compris le principe
* http://drupal.org/project/DAST 5 augmente le profile_generator
* http://drupal.org/project/deploy 5 
* http://drupal.org/project/profile_generator 5 celui que je connais
* http://drupal.org/project/install_profile_api 5+6 mais ne fait rien tout seul
* http://drupal.org/project/foaf 4 synchro de profils, domage car trop vieuw
* http://drupal.org/project/backup_migrate 5+6 permet de choisir les tables a exporter
* http://drupal.org/project/datasync 5+6 ne fait rien tout seul
* http://drupal.org/project/importexportapi 
* http://drupal.org/project/activitystream 6 avoir
* http://drupal.org/project/dbscripts 5 database merging ! a voir
* http://drupal.org/project/cvs_deploy a l'air inadapté
* http://drupal.org/project/migrator 5
* http://drupal.org/project/versioncontrol_project 5
* http://drupal.org/project/drush 5+6 pour scripter drupal, a voir
* http://drupal.org/project/sympal_scripts date de 2006 non maintenu

!31.10.08
* http://drupal.org/node/291373 montre comment partager des tables entres deux version d'un site (partage des utilisateurs)
* http://books.tag1consulting.com/scalability/drupal/start/staging sur base de GIT

!14.11.08
* http://www.geocities.com/mergedb/ un script de merge de base de prod et de dev... a voir, provenant de la : http://drupal.org/node/206066

!03.12.08
* http://drupal.org/project/nodeshare un nouveau module qui pourrait répondre a la question !</pre>
</div>
<div title="Versionning" modifier="YBA" created="200809221323" modified="200809221545" changecount="2">
<pre>! V1 22.09.08
* Outil de selection de liste (backlog)
* Burdown
* Saisie du poids fibonnacci
* Aide et préconfiguration scrum intégrée
! V1.1 22.09.08
* améliorations de l'aide
* améliorations des ViewTemplate (aide intégrée, meilleure structure des US, task, backlog)
* TaskBoard par sprint
* amélioration : les UserStory en texte (sans les double crochet) mais qui existent en tant que tiddler sont reconnues par le bouton Update -- au lieu d'être dupliquées</pre>
</div>
<div title="VirtualisationLog" modifier="YBA" created="200810151216" modified="200811071113" tags="doc" changecount="10">
<pre>!Mercredi 8 et Jeudi 9 octobre
* test d'un site drupal 5.11 avec du contenu généré par devel : 500 utilisateurs 500 contenus et 500 commentaires et des taxonomies par défaut
* installation d'une machine virtuelle (émulation virtualbox) avec ubuntu 8.04 32-bit
* test site machine virtuelle=&gt;machine virtuelle hôte=&gt;machine virtuelle et hôte=&gt;hôte avec l'outil de mesure de performance d'apache ab 
* perte de la moitié des performances pour le test du site placé sur la machine virtuelle émulée

!Vendredi 10 octobre
* tentative installation xen sur ubuntu
** reinstallation de grub à la main car pas automatique (bug)
** problèmes de réseau avec l'os et le noyau xen chargé
* installation virtualbox ubuntu server et test pour comparer les performances par rapport à un os avec une interface graphique =&gt; résultat à peut presque identique à la virtualbox avec ubuntu desktop (cela ne reste que de l'émulation il faut voir pour de la full ou de la paravirtualisation pour des résultat peut être plus significatifs)

!Lundi 13 octobre
* repartitionnement du disque pour faire 3 mini partition de ~20Go pour les tests
* installation d'une partion ubuntu réservée au tests de Xen (ubuntu 8.04 64-bit)
* gros problèmes de réseau sur l'os avec le noyau de XEN

!Mardi 14 octobre
* installation opensuse 11 
** attention aux partitions!! il a tendance à effacer toutes les autres partitions linux
** installation xen facilement avec l'interface de l'hyperviseur (attention à bien mettre à jour les pilotes graphiques avant xen et avant de relancer sous xen sinon énormes problèmes)
** reconfiguration de grub pour correspondre aux nouveau numéro de disque des autres os

* installation machine virtuelle opensuse 11
** installation apache2 mysql php
** configuration du serveur à la main
** pb même après installation du clavier en il reste en querty même pas anglais ( pb résolu pour avoir le clavier en azerty mettre le type de clavier sur USA?!? )
** le répertoire du serveur est changé  par rapport ubuntu /srv/www/htdocs pas de /var/www

!Mercredi 15 octobre
* installation d'un site de test drupal 5.11 sur la machine virtuelle de XEN, utilisation de  la base de donnée du site de test de Yohan sur KVM
** vm-&gt;vm ~ 8 et h-&gt;vm ~ 8 test h-&gt;h ~15 (car la machine paravirtualisée n'utilise qu'un processeur virtuel) 
** test de la machine virtuelle paravirtualisée avec 2 processeurs virtuels vm-&gt;vm~15 h-&gt;vm~15 
** donc il n'y a pas de perte significative  pour le passage d'une machine réelle à une machine paravirtualisée (attention rien à voir avec les machines émulées)
** petits problèmes au niveau du dernier package d'apache qui présente des bugs, nécessité de tout desinstaller et de réinstaller une version antérieure (celle stable de la distribution sur le cd) idem pour phpmyadmin dont le package est indisponible au telechargement
** problèmes de réseaux(brige entre dom0 et domU) et de firewall mais réglés également

* installation d'une nouvelle partition ubuntu(8.04 32-bit) pour kvm 

!Jeudi 16 octobre
* installation de kvm/qemu en ne suivant évidemment pas à la lettre le lien fournit
** pb de bios avec l'activation de la virtualisation, réglée par une mise à jour du bios
** installation de virt-manager pour l'interface, il est plus simple de choisir ses options pour le réseau et la configuration de la machine
** attention pas de paravirtualisation uniquement de la full virtualisation, on ne peut donc pas remodifier la machine après sa conception.
** performance du site de test (contenu identique)  kvm-&gt;kvm 18.03 h-&gt;h (19.08 17.58 18.05)
* test de performances entre drupal 5.11 et 6.5 sur une machine fullvirtualisée avec virt-manager et kvm/qemu (ubuntu 8.04 32-bit 512Mo 2 processeurs)
** les 2 sites à vide (juste installés sans le contenu) drupal5 -&gt; 23.60 et drupal6 -&gt; 22.3
** les 2 sites avec du contenu 500 utilisateurs, 30 taxo, 500 termes,  12 caractères maxi pour les titres des termes, 1000 nodes, 1000 commentaires, 8 mots maxi par titre 
drupal5 -&gt; 15.55 et drupal6 -&gt; 17.95
* test des caches 
** installation de memcached et test sur la machine hote 19.66 (? j'ai du louper qqchose dans la config ?)
** installation de acp et test -&gt;64.72 (whaooo !!!!)
* test nocturnes peu concluant : les test qui fonctionnait avec 1000 requetes ne fonctionnent plus  à 10000 avec un accès concurrent &gt;= 100

!Lundi 20 octobre
* test du site bleucom avec différents caches
** cache de drupal 5
** cache memcache
** cache apc : echec?
** essai de squid sans autres caches, les resultats ne sont pas concluants ( certainement erreur de config)

!Jeudi 30 octobre
*installation d'un site drupal 5 en conformité avec le site
** sans cache 9.60rps
** cache normal 230rps
** cache agressif 280 rps
* Avec Apc installé et sans cache =&gt; 20rps

!Vendredi 31 octobre
Test de Jmeter avec les tutoriaux de http://www.johnandcailin.com/blog/john/load-test-your-drupal-application-scalability-apache-jmeter
* création d'un test simple
** une seule page
** 10 accès concurrent
* création d'un test complexe
** 2types de threads browser/creator
** http proxy server pour plusieurs pages avec création de node et de commentaires</pre>
</div>
<div title="deprecated" modifier="YBA" created="200809191329" changecount="1" creator="YBA">
<pre>&lt;&lt;deleteAllTagged&gt;&gt;</pre>
</div>
<div title="sprint" modifier="YBA" created="200809191252" changecount="2">
<pre>&lt;&lt;deleteAllTagged&gt;&gt;</pre>
</div>
<div title="store.php" modifier="YBA" created="200809160809" tags="Upload" changecount="1">
<pre>&lt;?php
/***
! User settings
Edit these lines according to your need
***/
//{{{
$AUTHENTICATE_USER = true;	// true | false
$USERS = array(
	'UserName1'=&gt;'Password1', 
	'UserName2'=&gt;'Password2', 
	'UserName3'=&gt;'Password3'); // set usernames and strong passwords
$DEBUG = false;				// true | false
$CLEAN_BACKUP = true; 		// during backuping a file, remove overmuch backups
$FOLD_JS = true; 			// if javascript files have been expanded during download the fold them
error_reporting(E_ERROR | E_WARNING | E_PARSE);
//}}}
/***
!Code
No change needed under
***/
//{{{

/***
 * store.php - upload a file in this directory
 * version :1.6.1 - 2007/08/01 - BidiX@BidiX.info
 * 
 * see : 
 *	http://tiddlywiki.bidi.info/#UploadPlugin for usage
 *	http://www.php.net/manual/en/features.file-upload.php 
 *		for details on uploading files
 * usage : 
 *	POST  
 *		UploadPlugin[backupDir=&lt;backupdir&gt;;user=&lt;user&gt;;password=&lt;password&gt;;uploadir=&lt;uploaddir&gt;;[debug=1];;]
 *		userfile &lt;file&gt;
 *	GET
 *
 * each external javascript file included by download.php is change by a reference (src=...)
 *
 * Revision history
 * V1.6.1 - 2007/08/01
 * Enhancement: Add javascript folding
 * V1.6.0 - 2007/05/17
 * Enhancement: Add backup management
 * V1.5.2 - 2007/02/13
 * Enhancement: Add optional debug option in client parameters
 * V1.5.1 - 2007/02/01
 * Enhancement: Check value of file_uploads in php.ini. Thanks to Didier Corbière
 * V1.5.0 - 2007/01/15
 * Correct: a bug in moving uploadFile in uploadDir thanks to DaniGutiérrez for reporting
 * Refactoring
 * V 1.4.3 - 2006/10/17 
 * Test if $filename.lock exists for GroupAuthoring compatibility
 * return mtime, destfile and backupfile after the message line
 * V 1.4.2 - 2006/10/12
 *  add error_reporting(E_PARSE);
 * v 1.4.1 - 2006/03/15
 *	add chmo 0664 on the uploadedFile
 * v 1.4 - 2006/02/23
 * 	add uploaddir option :  a path for the uploaded file relative to the current directory
 *	backupdir is a relative path
 *	make recusively directories if necessary for backupDir and uploadDir
 * v 1.3 - 2006/02/17
 *	presence and value of user are checked with $USERS Array (thanks to PauloSoares)
 * v 1.2 - 2006/02/12 
  *	POST  
 *		UploadPlugin[backupDir=&lt;backupdir&gt;;user=&lt;user&gt;;password=&lt;password&gt;;]
 *		userfile &lt;file&gt;
*	if $AUTHENTICATE_USER
 *		presence and value of user and password are checked with 
 *		$USER and $PASSWORD
 * v 1.1 - 2005/12/23 
 *	POST  UploadPlugin[backupDir=&lt;backupdir&gt;]  userfile &lt;file&gt;
 * v 1.0 - 2005/12/12 
 *	POST userfile &lt;file&gt;
 *
 * Copyright (c) BidiX@BidiX.info 2005-2007
 ***/
//}}}

//{{{

if ($_SERVER['REQUEST_METHOD'] == 'GET') {
	/*
	 * GET Request
	 */
?&gt;
&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;&gt;
&lt;html&gt;
	&lt;head&gt;
		&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html;charset=utf-8&quot; &gt;
		&lt;title&gt;BidiX.info - TiddlyWiki UploadPlugin - Store script&lt;/title&gt;
	&lt;/head&gt;
	&lt;body&gt;
		&lt;p&gt;
		&lt;p&gt;store.php V 1.6.1
		&lt;p&gt;BidiX@BidiX.info
		&lt;p&gt;&amp;nbsp;&lt;/p&gt;
		&lt;p&gt;&amp;nbsp;&lt;/p&gt;
		&lt;p&gt;&amp;nbsp;&lt;/p&gt;
		&lt;p align=&quot;center&quot;&gt;This page is designed to upload a &lt;a href=&quot;http://www.tiddlywiki.com/&quot;&gt;TiddlyWiki&lt;a&gt;.&lt;/p&gt;
		&lt;p align=&quot;center&quot;&gt;for details see : &lt;a href=&quot;http://TiddlyWiki.bidix.info/#HowToUpload&quot;&gt;TiddlyWiki.bidix.info/#HowToUpload&lt;a&gt;.&lt;/p&gt;	
	&lt;/body&gt;
&lt;/html&gt;
&lt;?php
exit;
}

/*
 * POST Request
 */
	 
// Recursive mkdir
function mkdirs($dir) {
	if( is_null($dir) || $dir === &quot;&quot; ){
		return false;
	}
	if( is_dir($dir) || $dir === &quot;/&quot; ){
		return true;
	}
	if( mkdirs(dirname($dir)) ){
		return mkdir($dir);
	}
	return false;
}

function toExit() {
	global $DEBUG, $filename, $backupFilename, $options;
	if ($DEBUG) {
		echo (&quot;\nHere is some debugging info : \n&quot;);
		echo(&quot;\$filename : $filename \n&quot;);
		echo(&quot;\$backupFilename : $backupFilename \n&quot;);
		print (&quot;\$_FILES : \n&quot;);
		print_r($_FILES);
		print (&quot;\$options : \n&quot;);
		print_r($options);
}
exit;
}

function ParseTWFileDate($s) {
	// parse date element
	preg_match ( '/^(\d\d\d\d)(\d\d)(\d\d)\.(\d\d)(\d\d)(\d\d)/', $s , $m );
	// make a date object
	$d = mktime($m[4], $m[5], $m[6], $m[2], $m[3], $m[1]);
	// get the week number
	$w = date(&quot;W&quot;,$d);

	return array(
		'year' =&gt; $m[1], 
		'mon' =&gt; $m[2], 
		'mday' =&gt; $m[3], 
		'hours' =&gt; $m[4], 
		'minutes' =&gt; $m[5], 
		'seconds' =&gt; $m[6], 
		'week' =&gt; $w);
}

function cleanFiles($dirname, $prefix) {
	$now = getdate();
	$now['week'] = date(&quot;W&quot;);

	$hours = Array();
	$mday = Array();
	$year = Array();
	
	$toDelete = Array();

	// need files recent first
	$files = Array();
	($dir = opendir($dirname)) || die (&quot;can't open dir '$dirname'&quot;);
	while (false !== ($file = readdir($dir))) {
		if (preg_match(&quot;/^$prefix/&quot;, $file))
        array_push($files, $file);
    }
	$files = array_reverse($files);
	
	// decides for each file
	foreach ($files as $file) {
		$fileTime = ParseTWFileDate(substr($file,strpos($file, '.')+1,strrpos($file,'.') - strpos($file, '.') -1));
		if (($now['year'] == $fileTime['year']) &amp;&amp;
			($now['mon'] == $fileTime['mon']) &amp;&amp;
			($now['mday'] == $fileTime['mday']) &amp;&amp;
			($now['hours'] == $fileTime['hours']))
				continue;
		elseif (($now['year'] == $fileTime['year']) &amp;&amp;
			($now['mon'] == $fileTime['mon']) &amp;&amp;
			($now['mday'] == $fileTime['mday'])) {
				if (isset($hours[$fileTime['hours']]))
					array_push($toDelete, $file);
				else 
					$hours[$fileTime['hours']] = true;
			}
		elseif 	(($now['year'] == $fileTime['year']) &amp;&amp;
			($now['mon'] == $fileTime['mon'])) {
				if (isset($mday[$fileTime['mday']]))
					array_push($toDelete, $file);
				else
					$mday[$fileTime['mday']] = true;
			}
		else {
			if (isset($year[$fileTime['year']][$fileTime['mon']]))
				array_push($toDelete, $file);
			else
				$year[$fileTime['year']][$fileTime['mon']] = true;
		}
	}
	return $toDelete;
}

function replaceJSContentIn($content) {
	if (preg_match (&quot;/(.*?)&lt;!--DOWNLOAD-INSERT-FILE:\&quot;(.*?)\&quot;--&gt;&lt;script\s+type=\&quot;text\/javascript\&quot;&gt;(.*)/ms&quot;, $content,$matches)) {
		$front = $matches[1];
		$js = $matches[2];
		$tail = $matches[3];
		if (preg_match (&quot;/&lt;\/script&gt;(.*)/ms&quot;, $tail,$matches2)) {		
			$tail = $matches2[1];
		}
		$jsContent = &quot;&lt;script type=\&quot;text/javascript\&quot; src=\&quot;$js\&quot;&gt;&lt;/script&gt;&quot;;
		$tail = replaceJSContentIn($tail);
		return($front.$jsContent.$tail);
	}
	else
		return $content;
}

// Check if file_uploads is active in php config
if (ini_get('file_uploads') != '1') {
   echo &quot;Error : File upload is not active in php.ini\n&quot;;
   toExit();
}

// var definitions
$uploadDir = './';
$uploadDirError = false;
$backupError = false;
$optionStr = $_POST['UploadPlugin'];
$optionArr=explode(';',$optionStr);
$options = array();
$backupFilename = '';
$filename = $_FILES['userfile']['name'];
$destfile = $filename;

// get options
foreach($optionArr as $o) {
	list($key, $value) = split('=', $o);
	$options[$key] = $value;
}

// debug activated by client
if ($options['debug'] == 1) {
	$DEBUG = true;
}

// authenticate User
if (($AUTHENTICATE_USER)
	&amp;&amp; ((!$options['user']) || (!$options['password']) || ($USERS[$options['user']] != $options['password']))) {
	echo &quot;Error : UserName or Password do not match \n&quot;;
	echo &quot;UserName : [&quot;.$options['user']. &quot;] Password : [&quot;. $options['password'] . &quot;]\n&quot;;
	toExit();
}



// make uploadDir
if ($options['uploaddir']) {
	$uploadDir = $options['uploaddir'];
	// path control for uploadDir   
    if (!(strpos($uploadDir, &quot;../&quot;) === false)) {
        echo &quot;Error: directory to upload specifies a parent folder&quot;;
        toExit();
	}
	if (! is_dir($uploadDir)) {
		mkdirs($uploadDir);
	}
	if (! is_dir($uploadDir)) {
		echo &quot;UploadDirError : $uploadDirError - File NOT uploaded !\n&quot;;
		toExit();
	}
	if ($uploadDir{strlen($uploadDir)-1} != '/') {
		$uploadDir = $uploadDir . '/';
	}
}
$destfile = $uploadDir . $filename;

// backup existing file
if (file_exists($destfile) &amp;&amp; ($options['backupDir'])) {
	if (! is_dir($options['backupDir'])) {
		mkdirs($options['backupDir']);
		if (! is_dir($options['backupDir'])) {
			$backupError = &quot;backup mkdir error&quot;;
		}
	}
	$backupFilename = $options['backupDir'].'/'.substr($filename, 0, strrpos($filename, '.'))
				.date('.Ymd.His').substr($filename,strrpos($filename,'.'));
	rename($destfile, $backupFilename) or ($backupError = &quot;rename error&quot;);
	// remove overmuch backup
	if ($CLEAN_BACKUP) {
		$toDelete = cleanFiles($options['backupDir'], substr($filename, 0, strrpos($filename, '.')));
		foreach ($toDelete as $file) {
			$f = $options['backupDir'].'/'.$file;
			if($DEBUG) {
				echo &quot;delete : &quot;.$options['backupDir'].'/'.$file.&quot;\n&quot;;
			}
			unlink($options['backupDir'].'/'.$file);
		}
	}
}

// move uploaded file to uploadDir
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $destfile)) {
	if ($FOLD_JS) {
		// rewrite the file to replace JS content
		$fileContent = file_get_contents ($destfile);
		$fileContent = replaceJSContentIn($fileContent);
		if (!$handle = fopen($destfile, 'w')) {
	         echo &quot;Cannot open file ($destfile)&quot;;
	         exit;
	    }
	    if (fwrite($handle, $fileContent) === FALSE) {
	        echo &quot;Cannot write to file ($destfile)&quot;;
	        exit;
	    }
	    fclose($handle);
	}
    
	chmod($destfile, 0644);
	if($DEBUG) {
		echo &quot;Debug mode \n\n&quot;;
	}
	if (!$backupError) {
		echo &quot;0 - File successfully loaded in &quot; .$destfile. &quot;\n&quot;;
	} else {
		echo &quot;BackupError : $backupError - File successfully loaded in &quot; .$destfile. &quot;\n&quot;;
	}
	echo(&quot;destfile:$destfile \n&quot;);
	if (($backupFilename) &amp;&amp; (!$backupError)) {
		echo &quot;backupfile:$backupFilename\n&quot;;
	}
	$mtime = filemtime($destfile);
	echo(&quot;mtime:$mtime&quot;);
} 
else {
	echo &quot;Error : &quot; . $_FILES['error'].&quot; - File NOT uploaded !\n&quot;;

}
toExit();
//}}}
?&gt;</pre>
</div>
<div title="storeTiddler.php" modifier="YBA" created="200809160809" tags="Upload" changecount="1">
<pre>&lt;?php
/***
! User settings
Edit these lines according to your need
***/
//{{{
$AUTHENTICATE_USER = true;	// true | false
$USERS = array(
	'UserName1'=&gt;'Password1', 
	'UserName2'=&gt;'Password2', 
	'UserName3'=&gt;'Password3'); // set usernames and strong passwords
$DEBUG = false;				// true | false
$CLEAN_BACKUP = true; 		// during backuping a file, remove overmuch backups
error_reporting(E_ERROR | E_WARNING | E_PARSE);
//}}}
/***
!Code
No change needed under
***/
//{{{

/***
 * storeTiddler.php - upload a tiddler to a TiddlyWiki file in this directory
 * version: 1.2.1 - 2008-05-06 - BidiX@BidiX.info
 * 
 * tiddler is POST as &lt;FORM&gt; with :
 *	FORM = 
 *		title=&lt;the title of the tiddler&gt;
 *		tiddler=&lt;result of externalizeTiddler() : the div in StoreArea format&gt;
 *		[oldTitle=&lt;the previous title of the tiddler&gt;] 
 *		[fileName=&lt;tiddlyWiki filename&gt;] (default: index.html)
 *		[backupDir=&lt;backupdir&gt;] (default: .)
 *		[user=&lt;user&gt;] (no default)
 *		[password=&lt;password&gt;] (no default)
 *		[uploadir=&lt;uploaddir&gt;] (default: .)
 *		[debug=1]] (default: false)
 * see : 
 *	http://tiddlywiki.bidi.info/#UploadTiddlerPlugin for usage
 *  http://tiddlywiki.bidi.info/#UploadPlugin for parameter descriptions
 * usage : 
 *	POST FORM
 *		Update &lt;tiddler&gt; in &lt;fileName&gt; TiddlyWiki
 *	GET
 *		Display a form for 
 *
 * Revision history
 * V1.2.1 - 2008-05-06
 * bug correction : Filename is always 'index.html'. The fileName variable isn't used to initialize tiddlyWiki filename. 
 * V1.2.0 - 2008-03-23
 * Exclusive lock to serialize rewrite of file  
 * V1.1.0 - 2008/03/05
 * Delete tiddler with oldTitle
 * V1.0.0 - 2008/02/24
 * First public Version
 * V0.3.0 - 2008/02/23
 * minor adjustments
 * V0.2.0 - 2008/02/23
 * Correction bug on large regex
 * V0.1.0 - 2008/02/09
 * Initial: First working version
 * V0.0.1 - 2008/02/02
 * Initial: Proof Of Concept
 *
 * Copyright (c) BidiX@BidiX.info 2005-2008
 ***/
//}}}

//{{{

if ($_SERVER['REQUEST_METHOD'] == 'GET') {
	/*
	 * GET Request
	 */
?&gt;
&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;&gt;
&lt;html&gt;
	&lt;head&gt;
		&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html;charset=utf-8&quot; &gt;
		&lt;title&gt;BidiX.info - TiddlyWiki UploadTiddlerPlugin - Store script&lt;/title&gt;
	&lt;/head&gt;
	&lt;body&gt;
		&lt;p&gt;
		&lt;p&gt;storeTiddler.php V 1.2.0
		&lt;p&gt;BidiX@BidiX.info
		&lt;p&gt;&amp;nbsp;&lt;/p&gt;
		&lt;p&gt;&amp;nbsp;&lt;/p&gt;
		&lt;p&gt;&amp;nbsp;&lt;/p&gt;
		&lt;p align=&quot;center&quot;&gt;This page is designed to upload a &lt;a href=&quot;http://www.tiddlywiki.com/#Tiddler&quot;&gt;Tiddler&lt;a&gt;.&lt;/p&gt;
		&lt;p align=&quot;center&quot;&gt;for details see : &lt;a href=&quot;http://TiddlyWiki.bidix.info/#HowToUpload&quot;&gt;TiddlyWiki.bidix.info/#HowToUpload&lt;a&gt;.&lt;/p&gt;
		&lt;hr&gt;
		&lt;form action=&quot;storeTiddler.php&quot; method=POST&gt;
			&lt;center&gt;
				&lt;table&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;Title:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;title&quot; size=80&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;Tiddler (in StoreArea format):&lt;/td&gt;
						&lt;td&gt;&lt;TEXTAREA NAME=&quot;tiddler&quot; COLS=80 ROWS=10&gt;
&amp;lt;div title=&amp;quot;New Tiddler&amp;quot; modifier=&amp;quot;BidiX&amp;quot; created=&amp;quot;200802161401&amp;quot; tags=&amp;quot;test&amp;quot; changecount=&amp;quot;1&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;Type the text for &amp;#x27;New Tiddler&amp;#x27;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
						&lt;/TEXTAREA&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;Old Title:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;oldTitle&quot; size=80 value=''&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;fileName:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;fileName&quot; size=80&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;backupDir:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;backupDir&quot; size=80&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;user:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;user&quot; size=80&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;password:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;password&quot; size=80&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;uploadir:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;uploadir&quot; size=80&gt;&lt;/td&gt;
					&lt;/tr&gt;
					&lt;tr&gt;
						&lt;td align=RIGHT&gt;debug:&lt;/td&gt;
						&lt;td&gt;&lt;input type=TEXT name=&quot;debug&quot; size=80 value=1&gt;&lt;/td&gt;
					&lt;/tr&gt;
				&lt;/table&gt;
				&lt;input type=SUBMIT align=&quot;CENTER&quot; value=&quot;Upload tiddler&quot;&gt;
			&lt;/center&gt;
		&lt;/form&gt;
	&lt;/body&gt;
&lt;/html&gt;
&lt;?php
exit;
}

/*
 * POST Request
 */

/*
 * Functions included from store.php
 */

	// Recursive mkdir
	function mkdirs($dir) {
		if( is_null($dir) || $dir === &quot;&quot; ){
			return false;
		}
		if( is_dir($dir) || $dir === &quot;/&quot; ){
			return true;
		}
		if( mkdirs(dirname($dir)) ){
			return mkdir($dir);
		}
		return false;
	}

	function toExit() {
		global $DEBUG, $filename, $backupFilename, $options;
		if ($DEBUG) {
			echo (&quot;\nHere is some debugging info : \n&quot;);
			echo(&quot;\$filename : $filename \n&quot;);
			echo(&quot;\$backupFilename : $backupFilename \n&quot;);
			print (&quot;\$_FILES : \n&quot;);
			print_r($_FILES);
			print (&quot;\$options : \n&quot;);
			print_r($options);
	}
	exit;
	}

	function ParseTWFileDate($s) {
		// parse date element
		preg_match ( '/^(\d\d\d\d)(\d\d)(\d\d)\.(\d\d)(\d\d)(\d\d)/', $s , $m );
		// make a date object
		$d = mktime($m[4], $m[5], $m[6], $m[2], $m[3], $m[1]);
		// get the week number
		$w = date(&quot;W&quot;,$d);

		return array(
			'year' =&gt; $m[1], 
			'mon' =&gt; $m[2], 
			'mday' =&gt; $m[3], 
			'hours' =&gt; $m[4], 
			'minutes' =&gt; $m[5], 
			'seconds' =&gt; $m[6], 
			'week' =&gt; $w);
	}

	function cleanFiles($dirname, $prefix) {
		$now = getdate();
		$now['week'] = date(&quot;W&quot;);

		$hours = Array();
		$mday = Array();
		$year = Array();

		$toDelete = Array();

		// need files recent first
		$files = Array();
		($dir = opendir($dirname)) || die (&quot;can't open dir '$dirname'&quot;);
		while (false !== ($file = readdir($dir))) {
			if (preg_match(&quot;/^$prefix/&quot;, $file))
	        array_push($files, $file);
	    }
		$files = array_reverse($files);

		// decides for each file
		foreach ($files as $file) {
			$fileTime = ParseTWFileDate(substr($file,strpos($file, '.')+1,strrpos($file,'.') - strpos($file, '.') -1));
			if (($now['year'] == $fileTime['year']) &amp;&amp;
				($now['mon'] == $fileTime['mon']) &amp;&amp;
				($now['mday'] == $fileTime['mday']) &amp;&amp;
				($now['hours'] == $fileTime['hours']))
					continue;
			elseif (($now['year'] == $fileTime['year']) &amp;&amp;
				($now['mon'] == $fileTime['mon']) &amp;&amp;
				($now['mday'] == $fileTime['mday'])) {
					if (isset($hours[$fileTime['hours']]))
						array_push($toDelete, $file);
					else 
						$hours[$fileTime['hours']] = true;
				}
			elseif 	(($now['year'] == $fileTime['year']) &amp;&amp;
				($now['mon'] == $fileTime['mon'])) {
					if (isset($mday[$fileTime['mday']]))
						array_push($toDelete, $file);
					else
						$mday[$fileTime['mday']] = true;
				}
			else {
				if (isset($year[$fileTime['year']][$fileTime['mon']]))
					array_push($toDelete, $file);
				else
					$year[$fileTime['year']][$fileTime['mon']] = true;
			}
		}
		return $toDelete;
	}

/*
 * End Functions included from store.php
 */

/*
 * parse and print a TiddlyWiki file
 */

Function readTiddlyWiki($tw) {	
	if (preg_match (&quot;/(.*?&lt;div id=\&quot;storeArea\&quot;&gt;.*?)(&lt;div.*)/ms&quot;, $tw,$matches)) {
		$head = $matches[1];
		$h = $matches[2];
		$tiddlers = array();
		while (preg_match (&quot;/(.*?)(&lt;div title=\&quot;(.*?)\&quot;.*?&lt;\/div&gt;)(.*)/ms&quot;, $h,$matches)) {
			$h=$matches[4];
			$tiddlers[$matches[3]] = $matches[2];
		}
		$tail = ltrim($h);
	}
	else {
		echo(&quot;The file '$file' isn't a valid TiddlyWiki&quot;);
		toExit();
	}
	return array($head, $tiddlers ,$tail);
}

Function writeTiddlyWiki($head,$tiddlers,$tail) {
	$content = $head;
	sort($tiddlers);
	foreach ($tiddlers as $t =&gt; $c) {
		$content .= $c . &quot;\n&quot;;
	}
	$content .= $tail;
	return $content;
}

// var definitions
$uploadDir = './';
$uploadDirError = false;
$backupError = false;
$backupFilename = '';
$filename = &quot;index.html&quot;;
$destfile = $filename;

$options = $_POST; // for store.php name compatibility

// debug activated by client
if ($options['debug'] == 1) {
	$DEBUG = true;
}


// authenticate User
if (($AUTHENTICATE_USER)
	&amp;&amp; ((!$options['user']) || (!$options['password']) || ($USERS[$options['user']] != $options['password']))) {
	echo &quot;Error : UserName or Password do not match \n&quot;;
	echo &quot;UserName : [&quot;.$options['user']. &quot;] Password : [&quot;. $options['password'] . &quot;]\n&quot;;
	toExit();
}


if ($options['fileName'])
	$filename = $options['fileName'];


// make uploadDir
if ($options['uploaddir']) {
	$uploadDir = $options['uploaddir'];
	// path control for uploadDir   
    if (!(strpos($uploadDir, &quot;../&quot;) === false)) {
        echo &quot;Error: directory to upload specifies a parent folder&quot;;
        toExit();
	}
	if (! is_dir($uploadDir)) {
		mkdirs($uploadDir);
	}
	if (! is_dir($uploadDir)) {
		echo &quot;UploadDirError : $uploadDirError - File NOT uploaded !\n&quot;;
		toExit();
	}
	if ($uploadDir{strlen($uploadDir)-1} != '/') {
		$uploadDir = $uploadDir . '/';
	}
}
$destfile = $uploadDir . $filename;

// backup existing file
if (file_exists($destfile) &amp;&amp; ($options['backupDir'])) {
	if (! is_dir($options['backupDir'])) {
		mkdirs($options['backupDir']);
		if (! is_dir($options['backupDir'])) {
			$backupError = &quot;backup mkdir error&quot;;
		}
	}
	$backupFilename = $options['backupDir'].'/'.substr($filename, 0, strrpos($filename, '.'))
				.date('.Ymd.His').substr($filename,strrpos($filename,'.'));
	copy($destfile, $backupFilename) or ($backupError = &quot;rename error&quot;);
	// remove overmuch backup
	if ($CLEAN_BACKUP) {
		$toDelete = cleanFiles($options['backupDir'], substr($filename, 0, strrpos($filename, '.')));
		foreach ($toDelete as $file) {
			$f = $options['backupDir'].'/'.$file;
			if($DEBUG) {
				echo &quot;delete : &quot;.$options['backupDir'].'/'.$file.&quot;\n&quot;;
			}
			unlink($options['backupDir'].'/'.$file);
		}
	}
}


if (file_exists($destfile)) {
	$f = fopen($destfile,'r+');
	if (flock($f, LOCK_EX)) { 
		while (!feof($f)) {
		    $contents .= fread($f, 8192);
		}
		list($head,$tiddlers,$tail) = readTiddlyWiki($contents);
		$title = $_POST['title'];
		$oldTitle = $_POST['oldTitle'];
		if ($oldTitle &amp;&amp; ($title != $oldTitle)) {
			unset($tiddlers[$oldTitle]);
		}
		$tiddlers[$title] = stripslashes($_POST['tiddler']);
		$contents = writeTiddlyWiki($head,$tiddlers,$tail);
		if (!rewind($f)) {
			echo(&quot;rewind error&quot;);
			toExit();
		} 
		if (!ftruncate($f, 0)) {
			echo(&quot;ftruncate error&quot;);
			toExit();
		};
		if (!fwrite($f, $contents)) {
			echo(&quot;fwrite error&quot;);
			toExit();			
		}
		fclose($f);	// fclose also unlock the file
		if($DEBUG) {
			echo &quot;Debug mode \n\n&quot;;
		}
		if (!$backupError) {
			echo &quot;0 - Tiddler successfully updated in &quot; .$destfile. &quot;\n&quot;;
		} else {
			echo &quot;BackupError : $backupError - Tiddler successfully updated in &quot; .$destfile. &quot;\n&quot;;
		}
		echo(&quot;destfile:$destfile \n&quot;);
		if (($backupFilename) &amp;&amp; (!$backupError)) {
			echo &quot;backupfile:$backupFilename\n&quot;;
		}
		$mtime = filemtime($destfile);
		echo(&quot;mtime:$mtime&quot;);
	
	}
	else {
		echo &quot;Error : '&quot; . $filename . &quot;' couldn't be locked - File NOT updated !\n&quot;;
	}
}
else {
	echo &quot;Error : '&quot; . $filename . &quot;' doesn't exist - File NOT updated !\n&quot;;
}
toExit();

?&gt;</pre>
</div>
<div title="task" modifier="YBA" created="200809191252" changecount="1">
<pre>&lt;&lt;deleteAllTagged&gt;&gt;</pre>
</div>
<div title="zCookieJar" modifier="YBA" created="200809171404" modified="200903241649" creator="YBA" changecount="9">
<pre>/***
&lt;&lt;tiddler CookieManager&gt;&gt;
***/
/***
!!![[Baked cookies:|CookieManagerPlugin]]
^^Press {{smallform{&lt;&lt;cookieManager button&gt;&gt;}}} to save the current browser cookies... then hand-edit this section to customize the results.^^
***/

// 29 cookies saved on Wednesday, September 17th 2008 at 16:13:59 by YBA//
//^^(edit/remove username check and/or individual option settings as desired)^^//
//{{{
	//config.options.chkAllowBrowserCookies=false;
	config.options.chkArticlePanel=false;
	config.options.chkBacklog=false;
	config.options.chkBackstage=false;
	config.options.chkBrouillon=true;
	config.options.chkFeatures=false;
	//config.options.chkHttpReadOnly=false;
	config.options.chkMyCalendars=true;
	config.options.chkObj=false;
	config.options.chkSaveBackups=true;
	config.options.chkSliderOptionsPanel=false;
	config.options.chkSubjectPanel=false;
	config.options.chkTDPFloatingEntry=false;
	config.options.chkUploadLog=true;
	config.options.chkUploadTiddler=true;
	config.options.chkUploadTiddlerFromFile=true;
	config.options.chkpasUploadPassword=true;
	config.options.chktimeline0=true;
	config.options.chktimeline17=false;
	config.options.chktimeline23=false;
	config.options.pasUploadPassword=&quot;YBA&quot;;
	config.options.txtMainTab=&quot;Timeline&quot;;
	config.options.txtMoreTab=&quot;Shadowed&quot;;
	config.options.txtUploadFilename=&quot;VBC.html&quot;;
	config.options.txtUploadLogMaxLine=&quot;100&quot;;
	config.options.txtUploadStoreUrl=&quot;http://192.168.2.23/scrum/store.php&quot;;
	config.options.txtUploadTiddlerStoreUrl=&quot;http://192.168.2.23/scrum/storeTiddler.php&quot;;
	config.options.txtUploadUserName=&quot;YBA&quot;;
	config.options.txtUserName=&quot;YBA&quot;;
//}}}
</pre>
</div>
</div>
<!--POST-STOREAREA-->
<!--POST-BODY-START-->
<!--POST-BODY-END-->
<script id="jsArea" type="text/javascript">
//<![CDATA[
//
// Please note:
//
// * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
//   in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
//
// * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
//   without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
//

//--
//-- Configuration repository
//--

// Miscellaneous options
var config = {
	numRssItems: 20, // Number of items in the RSS feed
	animDuration: 400, // Duration of UI animations in milliseconds
	cascadeFast: 20, // Speed for cascade animations (higher == slower)
	cascadeSlow: 60, // Speed for EasterEgg cascade animations
	cascadeDepth: 5, // Depth of cascade animation
	locale: "en" // W3C language tag
};

// Hashmap of alternative parsers for the wikifier
config.parsers = {};

// Adaptors
config.adaptors = {};
config.defaultAdaptor = null;

// Backstage tasks
config.tasks = {};

// Annotations
config.annotations = {};

// Custom fields to be automatically added to new tiddlers
config.defaultCustomFields = {};

// Messages
config.messages = {
	messageClose: {},
	dates: {},
	tiddlerPopup: {}
};

// Options that can be set in the options panel and/or cookies
config.options = {
	chkRegExpSearch: false,
	chkCaseSensitiveSearch: false,
	chkIncrementalSearch: true,
	chkAnimate: true,
	chkSaveBackups: true,
	chkAutoSave: false,
	chkGenerateAnRssFeed: false,
	chkSaveEmptyTemplate: false,
	chkOpenInNewWindow: true,
	chkToggleLinks: false,
	chkHttpReadOnly: true,
	chkForceMinorUpdate: false,
	chkConfirmDelete: true,
	chkInsertTabs: false,
	chkUsePreForStorage: true, // Whether to use <pre> format for storage
	chkDisplayInstrumentation: false,
	txtBackupFolder: "",
	txtEditorFocus: "text",
	txtMainTab: "tabTimeline",
	txtMoreTab: "moreTabAll",
	txtMaxEditRows: "30",
	txtFileSystemCharSet: "UTF-8",
	txtTheme: ""
	};
config.optionsDesc = {};

// List of notification functions to be called when certain tiddlers are changed or deleted
config.notifyTiddlers = [
	{name: "StyleSheetLayout", notify: refreshStyles},
	{name: "StyleSheetColors", notify: refreshStyles},
	{name: "StyleSheet", notify: refreshStyles},
	{name: "StyleSheetPrint", notify: refreshStyles},
	{name: "PageTemplate", notify: refreshPageTemplate},
	{name: "SiteTitle", notify: refreshPageTitle},
	{name: "SiteSubtitle", notify: refreshPageTitle},
	{name: "ColorPalette", notify: refreshColorPalette},
	{name: null, notify: refreshDisplay}
];

// Default tiddler templates
var DEFAULT_VIEW_TEMPLATE = 1;
var DEFAULT_EDIT_TEMPLATE = 2;
config.tiddlerTemplates = {
	1: "ViewTemplate",
	2: "EditTemplate"
};

// More messages (rather a legacy layout that shouldn't really be like this)
config.views = {
	wikified: {
		tag: {}
	},
	editor: {
		tagChooser: {}
	}
};

// Backstage tasks
config.backstageTasks = ["save","sync","importTask","tweak","upgrade","plugins"];

// Macros; each has a 'handler' member that is inserted later
config.macros = {
	today: {},
	version: {},
	search: {sizeTextbox: 15},
	tiddler: {},
	tag: {},
	tags: {},
	tagging: {},
	timeline: {},
	allTags: {},
	list: {
		all: {},
		missing: {},
		orphans: {},
		shadowed: {},
		touched: {},
		filter: {}
	},
	closeAll: {},
	permaview: {},
	saveChanges: {},
	slider: {},
	option: {},
	options: {},
	newTiddler: {},
	newJournal: {},
	tabs: {},
	gradient: {},
	message: {},
	view: {defaultView: "text"},
	edit: {},
	tagChooser: {},
	toolbar: {},
	plugins: {},
	refreshDisplay: {},
	importTiddlers: {},
	upgrade: {
		source: "http://www.tiddlywiki.com/upgrade/",
		backupExtension: "pre.core.upgrade"
	},
	sync: {},
	annotations: {}
};

// Commands supported by the toolbar macro
config.commands = {
	closeTiddler: {},
	closeOthers: {},
	editTiddler: {},
	saveTiddler: {hideReadOnly: true},
	cancelTiddler: {},
	deleteTiddler: {hideReadOnly: true},
	permalink: {},
	references: {type: "popup"},
	jump: {type: "popup"},
	syncing: {type: "popup"},
	fields: {type: "popup"}
};

// Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
config.userAgent = navigator.userAgent.toLowerCase();
config.browser = {
	isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
	isGecko: config.userAgent.indexOf("gecko") != -1,
	ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
	isSafari: config.userAgent.indexOf("applewebkit") != -1,
	isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
	firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
	isOpera: config.userAgent.indexOf("opera") != -1,
	isLinux: config.userAgent.indexOf("linux") != -1,
	isUnix: config.userAgent.indexOf("x11") != -1,
	isMac: config.userAgent.indexOf("mac") != -1,
	isWindows: config.userAgent.indexOf("win") != -1
};

// Basic regular expressions
config.textPrimitives = {
	upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
	lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
	anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
	anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
};
if(config.browser.isBadSafari) {
	config.textPrimitives = {
		upperLetter: "[A-Z\u00c0-\u00de]",
		lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
		anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
		anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
	};
}
config.textPrimitives.sliceSeparator = "::";
config.textPrimitives.sectionSeparator = "##";
config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
config.textPrimitives.unWikiLink = "~";
config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
	config.textPrimitives.lowerLetter + "+" +
	config.textPrimitives.upperLetter +
	config.textPrimitives.anyLetter + "*)|(?:" +
	config.textPrimitives.upperLetter + "{2,}" +
	config.textPrimitives.lowerLetter + "+))";

config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");

config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
	config.textPrimitives.brackettedLink + ")|(?:" +
	config.textPrimitives.urlPattern + ")","mg");
config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
	config.textPrimitives.titledBrackettedLink + ")|(?:" +
	config.textPrimitives.brackettedLink + ")|(?:" +
	config.textPrimitives.urlPattern + ")","mg");

config.glyphs = {
	browsers: [
		function() {return config.browser.isIE;},
		function() {return true;}
	],
	currBrowser: null,
	codes: {
		downTriangle: ["\u25BC","\u25BE"],
		downArrow: ["\u2193","\u2193"],
		bentArrowLeft: ["\u2190","\u21A9"],
		bentArrowRight: ["\u2192","\u21AA"]
	}
};

//--
//-- Shadow tiddlers
//--

config.shadowTiddlers = {
	StyleSheet: "",
	MarkupPreHead: "",
	MarkupPostHead: "",
	MarkupPreBody: "",
	MarkupPostBody: "",
	TabTimeline: '<<timeline>>',
	TabAll: '<<list all>>',
	TabTags: '<<allTags excludeLists>>',
	TabMoreMissing: '<<list missing>>',
	TabMoreOrphans: '<<list orphans>>',
	TabMoreShadowed: '<<list shadowed>>',
	AdvancedOptions: '<<options>>',
	PluginManager: '<<plugins>>'
};

//--
//-- Translateable strings
//--

// Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone

merge(config.options,{
	txtUserName: "YourName"});

merge(config.tasks,{
	save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
	sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
	importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
	tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
	upgrade: {text: "upgrade", tooltip: "Upgrade TiddlyWiki core code", content: '<<upgrade>>'},
	plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
});

// Options that can be set in the options panel and/or cookies
merge(config.optionsDesc,{
	txtUserName: "Username for signing your edits",
	chkRegExpSearch: "Enable regular expressions for searches",
	chkCaseSensitiveSearch: "Case-sensitive searching",
	chkIncrementalSearch: "Incremental key-by-key searching",
	chkAnimate: "Enable animations",
	chkSaveBackups: "Keep backup file when saving changes",
	chkAutoSave: "Automatically save changes",
	chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
	chkSaveEmptyTemplate: "Generate an empty template when saving changes",
	chkOpenInNewWindow: "Open external links in a new window",
	chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
	chkHttpReadOnly: "Hide editing features when viewed over HTTP",
	chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
	chkConfirmDelete: "Require confirmation before deleting tiddlers",
	chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
	txtBackupFolder: "Name of folder to use for backups",
	txtMaxEditRows: "Maximum number of rows in edit boxes",
	txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});

merge(config.messages,{
	customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
	pluginError: "Error: %0",
	pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
	pluginForced: "Executed because forced via 'systemConfigForce' tag",
	pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
	nothingSelected: "Nothing is selected. You must select one or more items first",
	savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
	subtitleUnknown: "(unknown)",
	undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
	shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
	tiddlerLinkTooltip: "%0 - %1, %2",
	externalLinkTooltip: "External link to %0",
	noTags: "There are no tagged tiddlers",
	notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
	cantSaveError: "It's not possible to save changes. Possible reasons include:\n- your browser doesn't support saving (Firefox, Internet Explorer, Safari and Opera all work if properly configured)\n- the pathname to your TiddlyWiki file contains illegal characters\n- the TiddlyWiki HTML file has been moved or renamed",
	invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
	backupSaved: "Backup saved",
	backupFailed: "Failed to save backup file",
	rssSaved: "RSS feed saved",
	rssFailed: "Failed to save RSS feed file",
	emptySaved: "Empty template saved",
	emptyFailed: "Failed to save empty template file",
	mainSaved: "Main TiddlyWiki file saved",
	mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
	macroError: "Error in macro <<\%0>>",
	macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
	missingMacro: "No such macro",
	overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
	unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
	confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
	saveInstructions: "SaveChanges",
	unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
	tiddlerSaveError: "Error when saving tiddler '%0'",
	tiddlerLoadError: "Error when loading tiddler '%0'",
	wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
	invalidFieldName: "Invalid field name %0",
	fieldCannotBeChanged: "Field '%0' cannot be changed",
	loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'",
	upgradeDone: "The upgrade to version %0 is now complete\n\nClick 'OK' to reload the newly upgraded TiddlyWiki"});

merge(config.messages.messageClose,{
	text: "close",
	tooltip: "close this message area"});

config.messages.backstage = {
	open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
	close: {text: "close", tooltip: "Close the backstage area"},
	prompt: "backstage: ",
	decal: {
		edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
	}
};

config.messages.listView = {
	tiddlerTooltip: "Click for the full text of this tiddler",
	previewUnavailable: "(preview not available)"
};

config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
		"th","th","th","th","th","th","th","th","th","th",
		"st","nd","rd","th","th","th","th","th","th","th",
		"st"];
config.messages.dates.am = "am";
config.messages.dates.pm = "pm";

merge(config.messages.tiddlerPopup,{
	});

merge(config.views.wikified.tag,{
	labelNoTags: "no tags",
	labelTags: "tags: ",
	openTag: "Open tag '%0'",
	tooltip: "Show tiddlers tagged with '%0'",
	openAllText: "Open all",
	openAllTooltip: "Open all of these tiddlers",
	popupNone: "No other tiddlers tagged with '%0'"});

merge(config.views.wikified,{
	defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
	defaultModifier: "(missing)",
	shadowModifier: "(built-in shadow tiddler)",
	dateFormat: "DD MMM YYYY",
	createdPrompt: "created"});

merge(config.views.editor,{
	tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
	defaultText: "Type the text for '%0'"});

merge(config.views.editor.tagChooser,{
	text: "tags",
	tooltip: "Choose existing tags to add to this tiddler",
	popupNone: "There are no tags defined",
	tagTooltip: "Add the tag '%0'"});

merge(config.messages,{
	sizeTemplates:
		[
		{unit: 1024*1024*1024, template: "%0\u00a0GB"},
		{unit: 1024*1024, template: "%0\u00a0MB"},
		{unit: 1024, template: "%0\u00a0KB"},
		{unit: 1, template: "%0\u00a0B"}
		]});

merge(config.macros.search,{
	label: "search",
	prompt: "Search this TiddlyWiki",
	accessKey: "F",
	successMsg: "%0 tiddlers found matching %1",
	failureMsg: "No tiddlers found matching %0"});

merge(config.macros.tagging,{
	label: "tagging: ",
	labelNotTag: "not tagging",
	tooltip: "List of tiddlers tagged with '%0'"});

merge(config.macros.timeline,{
	dateFormat: "DD MMM YYYY"});

merge(config.macros.allTags,{
	tooltip: "Show tiddlers tagged with '%0'",
	noTags: "There are no tagged tiddlers"});

config.macros.list.all.prompt = "All tiddlers in alphabetical order";
config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
config.macros.list.touched.prompt = "Tiddlers that have been modified locally";

merge(config.macros.closeAll,{
	label: "close all",
	prompt: "Close all displayed tiddlers (except any that are being edited)"});

merge(config.macros.permaview,{
	label: "permaview",
	prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});

merge(config.macros.saveChanges,{
	label: "save changes",
	prompt: "Save all tiddlers to create a new TiddlyWiki",
	accessKey: "S"});

merge(config.macros.newTiddler,{
	label: "new tiddler",
	prompt: "Create a new tiddler",
	title: "New Tiddler",
	accessKey: "N"});

merge(config.macros.newJournal,{
	label: "new journal",
	prompt: "Create a new tiddler from the current date and time",
	accessKey: "J"});

merge(config.macros.options,{
	wizardTitle: "Tweak advanced options",
	step1Title: "These options are saved in cookies in your browser",
	step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
	unknownDescription: "//(unknown)//",
	listViewTemplate: {
		columns: [
			{name: 'Option', field: 'option', title: "Option", type: 'String'},
			{name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
			{name: 'Name', field: 'name', title: "Name", type: 'String'}
			],
		rowClasses: [
			{className: 'lowlight', field: 'lowlight'}
			]}
	});

merge(config.macros.plugins,{
	wizardTitle: "Manage plugins",
	step1Title: "Currently loaded plugins",
	step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
	skippedText: "(This plugin has not been executed because it was added since startup)",
	noPluginText: "There are no plugins installed",
	confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
	removeLabel: "remove systemConfig tag",
	removePrompt: "Remove systemConfig tag",
	deleteLabel: "delete",
	deletePrompt: "Delete these tiddlers forever",
	listViewTemplate: {
		columns: [
			{name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
			{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
			{name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
			{name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
			{name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
			{name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
			{name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
			{name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
			{name: 'Log', field: 'log', title: "Log", type: 'StringList'}
			],
		rowClasses: [
			{className: 'error', field: 'error'},
			{className: 'warning', field: 'warning'}
			]}
	});

merge(config.macros.toolbar,{
	moreLabel: "more",
	morePrompt: "Reveal further commands"
	});

merge(config.macros.refreshDisplay,{
	label: "refresh",
	prompt: "Redraw the entire TiddlyWiki display"
	});

merge(config.macros.importTiddlers,{
	readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
	wizardTitle: "Import tiddlers from another file or server",
	step1Title: "Step 1: Locate the server or TiddlyWiki file",
	step1Html: "Specify the type of the server: <select name='selTypes'><option value=''>Choose...</option></select><br>Enter the URL or pathname here: <input type='text' size=50 name='txtPath'><br>...or browse for a file: <input type='file' size=50 name='txtBrowse'><br><hr>...or select a pre-defined feed: <select name='selFeeds'><option value=''>Choose...</option></select>",
	openLabel: "open",
	openPrompt: "Open the connection to this file or server",
	openError: "There were problems fetching the tiddlywiki file",
	statusOpenHost: "Opening the host",
	statusGetWorkspaceList: "Getting the list of available workspaces",
	step2Title: "Step 2: Choose the workspace",
	step2Html: "Enter a workspace name: <input type='text' size=50 name='txtWorkspace'><br>...or select a workspace: <select name='selWorkspace'><option value=''>Choose...</option></select>",
	cancelLabel: "cancel",
	cancelPrompt: "Cancel this import",
	statusOpenWorkspace: "Opening the workspace",
	statusGetTiddlerList: "Getting the list of available tiddlers",
	errorGettingTiddlerList: "Error getting list of tiddlers, click Cancel to try again",
	step3Title: "Step 3: Choose the tiddlers to import",
	step3Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='true' name='chkSync'>Keep these tiddlers linked to this server so that you can synchronise subsequent changes</input><br><input type='checkbox' name='chkSave'>Save the details of this server in a 'systemServer' tiddler called:</input> <input type='text' size=25 name='txtSaveTiddler'>",
	importLabel: "import",
	importPrompt: "Import these tiddlers",
	confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
	step4Title: "Step 4: Importing %0 tiddler(s)",
	step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
	doneLabel: "done",
	donePrompt: "Close this wizard",
	statusDoingImport: "Importing tiddlers",
	statusDoneImport: "All tiddlers imported",
	systemServerNamePattern: "%2 on %1",
	systemServerNamePatternNoWorkspace: "%1",
	confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
	serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
	serverSaveModifier: "(System)",
	listViewTemplate: {
		columns: [
			{name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
			{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
			{name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
			{name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
			],
		rowClasses: [
			]}
	});

merge(config.macros.upgrade,{
	wizardTitle: "Upgrade TiddlyWiki core code",
	step1Title: "Update or repair this TiddlyWiki to the latest release",
	step1Html: "You are about to upgrade to the latest release of the TiddlyWiki core code (from <a href='%0' class='externalLink' target='_blank'>%1</a>). Your content will be preserved across the upgrade.<br><br>Note that core upgrades have been known to interfere with older plugins. If you run into problems with the upgraded file, see <a href='http://www.tiddlywiki.org/wiki/CoreUpgrades' class='externalLink' target='_blank'>http://www.tiddlywiki.org/wiki/CoreUpgrades</a>",
	errorCantUpgrade: "Unable to upgrade this TiddlyWiki. You can only perform upgrades on TiddlyWiki files stored locally",
	errorNotSaved: "You must save changes before you can perform an upgrade",
	step2Title: "Confirm the upgrade details",
	step2Html_downgrade: "You are about to downgrade to TiddlyWiki version %0 from %1.<br><br>Downgrading to an earlier version of the core code is not recommended",
	step2Html_restore: "This TiddlyWiki appears to be already using the latest version of the core code (%0).<br><br>You can continue to upgrade anyway to ensure that the core code hasn't been corrupted or damaged",
	step2Html_upgrade: "You are about to upgrade to TiddlyWiki version %0 from %1",
	upgradeLabel: "upgrade",
	upgradePrompt: "Prepare for the upgrade process",
	statusPreparingBackup: "Preparing backup",
	statusSavingBackup: "Saving backup file",
	errorSavingBackup: "There was a problem saving the backup file",
	statusLoadingCore: "Loading core code",
	errorLoadingCore: "Error loading the core code",
	errorCoreFormat: "Error with the new core code",
	statusSavingCore: "Saving the new core code",
	statusReloadingCore: "Reloading the new core code",
	startLabel: "start",
	startPrompt: "Start the upgrade process",
	cancelLabel: "cancel",
	cancelPrompt: "Cancel the upgrade process",
	step3Title: "Upgrade cancelled",
	step3Html: "You have cancelled the upgrade process"
	});

merge(config.macros.sync,{
	listViewTemplate: {
		columns: [
			{name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
			{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
			{name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
			{name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
			{name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
			{name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
			{name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
			],
		rowClasses: [
			],
		buttons: [
			{caption: "Sync these tiddlers", name: 'sync'}
			]},
	wizardTitle: "Synchronize with external servers and files",
	step1Title: "Choose the tiddlers you want to synchronize",
	step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
	syncLabel: "sync",
	syncPrompt: "Sync these tiddlers",
	hasChanged: "Changed while unplugged",
	hasNotChanged: "Unchanged while unplugged",
	syncStatusList: {
		none: {text: "...", color: "transparent"},
		changedServer: {text: "Changed on server", color: '#80ff80'},
		changedLocally: {text: "Changed while unplugged", color: '#80ff80'},
		changedBoth: {text: "Changed while unplugged and on server", color: '#ff8080'},
		notFound: {text: "Not found on server", color: '#ffff80'},
		putToServer: {text: "Saved update on server", color: '#ff80ff'},
		gotFromServer: {text: "Retrieved update from server", color: '#80ffff'}
		}
	});

merge(config.macros.annotations,{
	});

merge(config.commands.closeTiddler,{
	text: "close",
	tooltip: "Close this tiddler"});

merge(config.commands.closeOthers,{
	text: "close others",
	tooltip: "Close all other tiddlers"});

merge(config.commands.editTiddler,{
	text: "edit",
	tooltip: "Edit this tiddler",
	readOnlyText: "view",
	readOnlyTooltip: "View the source of this tiddler"});

merge(config.commands.saveTiddler,{
	text: "done",
	tooltip: "Save changes to this tiddler"});

merge(config.commands.cancelTiddler,{
	text: "cancel",
	tooltip: "Undo changes to this tiddler",
	warning: "Are you sure you want to abandon your changes to '%0'?",
	readOnlyText: "done",
	readOnlyTooltip: "View this tiddler normally"});

merge(config.commands.deleteTiddler,{
	text: "delete",
	tooltip: "Delete this tiddler",
	warning: "Are you sure you want to delete '%0'?"});

merge(config.commands.permalink,{
	text: "permalink",
	tooltip: "Permalink for this tiddler"});

merge(config.commands.references,{
	text: "references",
	tooltip: "Show tiddlers that link to this one",
	popupNone: "No references"});

merge(config.commands.jump,{
	text: "jump",
	tooltip: "Jump to another open tiddler"});

merge(config.commands.syncing,{
	text: "syncing",
	tooltip: "Control synchronisation of this tiddler with a server or external file",
	currentlySyncing: "<div>Currently syncing via <span class='popupHighlight'>'%0'</span> to:</"+"div><div>host: <span class='popupHighlight'>%1</span></"+"div><div>workspace: <span class='popupHighlight'>%2</span></"+"div>", // Note escaping of closing <div> tag
	notCurrentlySyncing: "Not currently syncing",
	captionUnSync: "Stop synchronising this tiddler",
	chooseServer: "Synchronise this tiddler with another server:",
	currServerMarker: "\u25cf ",
	notCurrServerMarker: "  "});

merge(config.commands.fields,{
	text: "fields",
	tooltip: "Show the extended fields of this tiddler",
	emptyText: "There are no extended fields for this tiddler",
	listViewTemplate: {
		columns: [
			{name: 'Field', field: 'field', title: "Field", type: 'String'},
			{name: 'Value', field: 'value', title: "Value", type: 'String'}
			],
		rowClasses: [
			],
		buttons: [
			]}});

merge(config.shadowTiddlers,{
	DefaultTiddlers: "GettingStarted",
	MainMenu: "GettingStarted",
	SiteTitle: "My TiddlyWiki",
	SiteSubtitle: "a reusable non-linear personal web notebook",
	SiteUrl: "http://www.tiddlywiki.com/",
	SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options \u00bb" "Change TiddlyWiki advanced options">>',
	SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
	TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>',
	ToolbarCommands: "|~ViewToolbar|closeTiddler closeOthers +editTiddler > fields syncing permalink references jump|\n|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|"});

merge(config.annotations,{
	AdvancedOptions: "This shadow tiddler provides access to several advanced options",
	ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
	DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
	EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
	GettingStarted: "This shadow tiddler provides basic usage instructions",
	ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
	MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
	MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
	MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
	MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
	MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately after the script block",
	OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
	PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
	PluginManager: "This shadow tiddler provides access to the plugin manager",
	SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
	SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
	SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
	SiteTitle: "This shadow tiddler is used as the first part of the page title",
	SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
	StyleSheetColors: "This shadow tiddler contains CSS definitions related to the color of page elements. ''DO NOT EDIT THIS TIDDLER'', instead make your changes in the StyleSheet shadow tiddler",
	StyleSheet: "This tiddler can contain custom CSS definitions",
	StyleSheetLayout: "This shadow tiddler contains CSS definitions related to the layout of page elements. ''DO NOT EDIT THIS TIDDLER'', instead make your changes in the StyleSheet shadow tiddler",
	StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
	StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
	TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
	TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
	TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
	TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
	TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
	TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
	TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
	ToolbarCommands: "This shadow tiddler determines which commands are shown in tiddler toolbars",
	ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
	});

//--
//-- Main
//--

var params = null; // Command line parameters
var store = null; // TiddlyWiki storage
var story = null; // Main story
var formatter = null; // Default formatters for the wikifier
var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
var readOnly = false; // Whether we're in readonly mode
var highlightHack = null; // Embarrassing hack department...
var hadConfirmExit = false; // Don't warn more than once
var safeMode = false; // Disable all plugins and cookies
var showBackstage; // Whether to include the backstage area
var installedPlugins = []; // Information filled in when plugins are executed
var startingUp = false; // Whether we're in the process of starting up
var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()

// Whether to use the JavaSaver applet
var useJavaSaver = (config.browser.isSafari || config.browser.isOpera) && (document.location.toString().substr(0,4) != "http");

// Starting up
function main()
{
	var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
	startingUp = true;
	window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
	params = getParameters();
	if(params)
		params = params.parseParams("open",null,false);
	store = new TiddlyWiki();
	invokeParamifier(params,"oninit");
	story = new Story("tiddlerDisplay","tiddler");
	addEvent(document,"click",Popup.onDocumentClick);
	saveTest();
	loadOptionsCookie();
	for(var s=0; s<config.notifyTiddlers.length; s++)
		store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
	t1 = new Date();
	loadShadowTiddlers();
	t2 = new Date();
	store.loadFromDiv("storeArea","store",true);
	t3 = new Date();
	invokeParamifier(params,"onload");
	t4 = new Date();
	readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
	showBackstage = !readOnly;
	var pluginProblem = loadPlugins();
	t5 = new Date();
	formatter = new Formatter(config.formatters);
	story.switchTheme(config.options.txtTheme);
	invokeParamifier(params,"onconfig");
	t6 = new Date();
	store.notifyAll();
	t7 = new Date();
	restart();
	refreshDisplay();
	t8 = new Date();
	if(pluginProblem) {
		story.displayTiddler(null,"PluginManager");
		displayMessage(config.messages.customConfigError);
	}
	for(var m in config.macros) {
		if(config.macros[m].init)
			config.macros[m].init();
	}
	t9 = new Date();
	if(showBackstage)
		backstage.init();
	t10 = new Date();
	if(config.options.chkDisplayInstrumentation) {
		displayMessage("LoadShadows " + (t2-t1) + " ms");
		displayMessage("LoadFromDiv " + (t3-t2) + " ms");
		displayMessage("LoadPlugins " + (t5-t4) + " ms");
		displayMessage("Notify " + (t7-t6) + " ms");
		displayMessage("Restart " + (t8-t7) + " ms");
		displayMessage("Macro init " + (t9-t8) + " ms");
		displayMessage("Total: " + (t10-t0) + " ms");
	}
	startingUp = false;
}

// Restarting
function restart()
{
	invokeParamifier(params,"onstart");
	if(story.isEmpty()) {
		var tiddlers = store.filterTiddlers(store.getTiddlerText("DefaultTiddlers"));
		story.displayTiddlers(null,tiddlers);
	}
	window.scrollTo(0,0);
}

function saveTest()
{
	var s = document.getElementById("saveTest");
	if(s.hasChildNodes())
		alert(config.messages.savedSnapshotError);
	s.appendChild(document.createTextNode("savetest"));
}

function loadShadowTiddlers()
{
	var shadows = new TiddlyWiki();
	shadows.loadFromDiv("shadowArea","shadows",true);
	shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
	delete shadows;
}

function loadPlugins()
{
	if(safeMode)
		return false;
	var tiddlers = store.getTaggedTiddlers("systemConfig");
	var toLoad = [];
	var nLoaded = 0;
	var map = {};
	var nPlugins = tiddlers.length;
	installedPlugins = [];
	for(var i=0; i<nPlugins; i++) {
		var p = getPluginInfo(tiddlers[i]);
		installedPlugins[i] = p;
		var n = p.Name;
		if(n)
			map[n] = p;
		n = p.Source;
		if(n)
			map[n] = p;
	}
	var visit = function(p) {
		if(!p || p.done)
			return;
		p.done = 1;
		var reqs = p.Requires;
		if(reqs) {
			reqs = reqs.readBracketedList();
			for(var i=0; i<reqs.length; i++)
				visit(map[reqs[i]]);
		}
		toLoad.push(p);
	};
	for(i=0; i<nPlugins; i++)
		visit(installedPlugins[i]);
	for(i=0; i<toLoad.length; i++) {
		p = toLoad[i];
		pluginInfo = p;
		tiddler = p.tiddler;
		if(isPluginExecutable(p)) {
			if(isPluginEnabled(p)) {
				p.executed = true;
				var startTime = new Date();
				try {
					if(tiddler.text)
						window.eval(tiddler.text);
					nLoaded++;
				} catch(ex) {
					p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
					p.error = true;
				}
				pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
			} else {
				nPlugins--;
			}
		} else {
			p.warning = true;
		}
	}
	return nLoaded != nPlugins;
}

function getPluginInfo(tiddler)
{
	var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
	p.tiddler = tiddler;
	p.title = tiddler.title;
	p.log = [];
	return p;
}

// Check that a particular plugin is valid for execution
function isPluginExecutable(plugin)
{
	if(plugin.tiddler.isTagged("systemConfigForce"))
		return verifyTail(plugin,true,config.messages.pluginForced);
	if(plugin["CoreVersion"]) {
		var coreVersion = plugin["CoreVersion"].split(".");
		var w = parseInt(coreVersion[0]) - version.major;
		if(w == 0 && coreVersion[1])
			w = parseInt(coreVersion[1]) - version.minor;
		if(w == 0 && coreVersion[2])
			w = parseInt(coreVersion[2]) - version.revision;
		if(w > 0)
			return verifyTail(plugin,false,config.messages.pluginVersionError);
		}
	return true;
}

function isPluginEnabled(plugin)
{
	if(plugin.tiddler.isTagged("systemConfigDisable"))
		return verifyTail(plugin,false,config.messages.pluginDisabled);
	return true;
}

function verifyTail(plugin,result,message)
{
	plugin.log.push(message);
	return result;
}

function invokeMacro(place,macro,params,wikifier,tiddler)
{
	try {
		var m = config.macros[macro];
		if(m && m.handler)
			m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
		else
			createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
	} catch(ex) {
		createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
	}
}

//--
//-- Paramifiers
//--

function getParameters()
{
	var p = null;
	if(window.location.hash) {
		p = decodeURIComponent(window.location.hash.substr(1));
		if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
			p = convertUTF8ToUnicode(p);
	}
	return p;
}

function invokeParamifier(params,handler)
{
	if(!params || params.length == undefined || params.length <= 1)
		return;
	for(var t=1; t<params.length; t++) {
		var p = config.paramifiers[params[t].name];
		if(p && p[handler] instanceof Function)
			p[handler](params[t].value);
	}
}

config.paramifiers = {};

config.paramifiers.start = {
	oninit: function(v) {
		safeMode = v.toLowerCase() == "safe";
	}
};

config.paramifiers.open = {
	onstart: function(v) {
		if(!readOnly || store.tiddlerExists(v) || store.isShadowTiddler(v))
			story.displayTiddler("bottom",v,null,false,null);
	}
};

config.paramifiers.story = {
	onstart: function(v) {
		var list = store.getTiddlerText(v,"").parseParams("open",null,false);
		invokeParamifier(list,"onstart");
	}
};

config.paramifiers.search = {
	onstart: function(v) {
		story.search(v,false,false);
	}
};

config.paramifiers.searchRegExp = {
	onstart: function(v) {
		story.prototype.search(v,false,true);
	}
};

config.paramifiers.tag = {
	onstart: function(v) {
		var tagged = store.getTaggedTiddlers(v,"title");
		story.displayTiddlers(null,tagged,null,false,null);
	}
};

config.paramifiers.newTiddler = {
	onstart: function(v) {
		if(!readOnly) {
			story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
			story.focusTiddler(v,"text");
		}
	}
};

config.paramifiers.newJournal = {
	onstart: function(v) {
		if(!readOnly) {
			var now = new Date();
			var title = now.formatString(v.trim());
			story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
			story.focusTiddler(title,"text");
		}
	}
};

config.paramifiers.readOnly = {
	onconfig: function(v) {
		var p = v.toLowerCase();
		readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
	}
};

config.paramifiers.theme = {
	onconfig: function(v) {
		story.switchTheme(v);
	}
};

config.paramifiers.upgrade = {
	onstart: function(v) {
		upgradeFrom(v);
	}
};

//--
//-- Formatter helpers
//--

function Formatter(formatters)
{
	this.formatters = [];
	var pattern = [];
	for(var n=0; n<formatters.length; n++) {
		pattern.push("(" + formatters[n].match + ")");
		this.formatters.push(formatters[n]);
	}
	this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
}

config.formatterHelpers = {

	createElementAndWikify: function(w)
	{
		w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
	},

	inlineCssHelper: function(w)
	{
		var styles = [];
		config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
		var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
		while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
			var s,v;
			if(lookaheadMatch[1]) {
				s = lookaheadMatch[1].unDash();
				v = lookaheadMatch[2];
			} else {
				s = lookaheadMatch[3].unDash();
				v = lookaheadMatch[4];
			}
			if(s=="bgcolor")
				s = "backgroundColor";
			styles.push({style: s, value: v});
			w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
			config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
			lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
		}
		return styles;
	},

	applyCssHelper: function(e,styles)
	{
		for(var t=0; t< styles.length; t++) {
			try {
				e.style[styles[t].style] = styles[t].value;
			} catch (ex) {
			}
		}
	},

	enclosedTextHelper: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var text = lookaheadMatch[1];
			if(config.browser.isIE)
				text = text.replace(/\n/g,"\r");
			createTiddlyElement(w.output,this.element,null,null,text);
			w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
		}
	},

	isExternalLink: function(link)
	{
		if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
			return false;
		}
		var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
		if(urlRegExp.exec(link)) {
			return true;
		}
		if(link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1 || link.indexOf("#")!=-1) {
			return true;
		}
		return false;
	}

};

//--
//-- Standard formatters
//--

config.formatters = [
{
	name: "table",
	match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
	lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
	rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
	cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
	cellTermRegExp: /((?:\x20*)\|)/mg,
	rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
	handler: function(w)
	{
		var table = createTiddlyElement(w.output,"table",null,"twtable");
		var prevColumns = [];
		var currRowType = null;
		var rowContainer;
		var rowCount = 0;
		w.nextMatch = w.matchStart;
		this.lookaheadRegExp.lastIndex = w.nextMatch;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
			var nextRowType = lookaheadMatch[2];
			if(nextRowType == "k") {
				table.className = lookaheadMatch[1];
				w.nextMatch += lookaheadMatch[0].length+1;
			} else {
				if(nextRowType != currRowType) {
					rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
					currRowType = nextRowType;
				}
				if(currRowType == "c") {
					// Caption
					w.nextMatch++;
					if(rowContainer != table.firstChild)
						table.insertBefore(rowContainer,table.firstChild);
					rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
					w.subWikifyTerm(rowContainer,this.rowTermRegExp);
				} else {
					var theRow = createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow");
					theRow.onmouseover = function() {addClass(this,"hoverRow");};
					theRow.onmouseout = function() {removeClass(this,"hoverRow");};
					this.rowHandler(w,theRow,prevColumns);
					rowCount++;
				}
			}
			this.lookaheadRegExp.lastIndex = w.nextMatch;
			lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		}
	},
	rowHandler: function(w,e,prevColumns)
	{
		var col = 0;
		var colSpanCount = 1;
		var prevCell = null;
		this.cellRegExp.lastIndex = w.nextMatch;
		var cellMatch = this.cellRegExp.exec(w.source);
		while(cellMatch && cellMatch.index == w.nextMatch) {
			if(cellMatch[1] == "~") {
				// Rowspan
				var last = prevColumns[col];
				if(last) {
					last.rowSpanCount++;
					last.element.setAttribute("rowspan",last.rowSpanCount);
					last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
					last.element.valign = "center";
				}
				w.nextMatch = this.cellRegExp.lastIndex-1;
			} else if(cellMatch[1] == ">") {
				// Colspan
				colSpanCount++;
				w.nextMatch = this.cellRegExp.lastIndex-1;
			} else if(cellMatch[2]) {
				// End of row
				if(prevCell && colSpanCount > 1) {
					prevCell.setAttribute("colspan",colSpanCount);
					prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
				}
				w.nextMatch = this.cellRegExp.lastIndex;
				break;
			} else {
				// Cell
				w.nextMatch++;
				var styles = config.formatterHelpers.inlineCssHelper(w);
				var spaceLeft = false;
				var chr = w.source.substr(w.nextMatch,1);
				while(chr == " ") {
					spaceLeft = true;
					w.nextMatch++;
					chr = w.source.substr(w.nextMatch,1);
				}
				var cell;
				if(chr == "!") {
					cell = createTiddlyElement(e,"th");
					w.nextMatch++;
				} else {
					cell = createTiddlyElement(e,"td");
				}
				prevCell = cell;
				prevColumns[col] = {rowSpanCount:1,element:cell};
				if(colSpanCount > 1) {
					cell.setAttribute("colspan",colSpanCount);
					cell.setAttribute("colSpan",colSpanCount); // Needed for IE
					colSpanCount = 1;
				}
				config.formatterHelpers.applyCssHelper(cell,styles);
				w.subWikifyTerm(cell,this.cellTermRegExp);
				if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
					cell.align = spaceLeft ? "center" : "left";
				else if(spaceLeft)
					cell.align = "right";
				w.nextMatch--;
			}
			col++;
			this.cellRegExp.lastIndex = w.nextMatch;
			cellMatch = this.cellRegExp.exec(w.source);
		}
	}
},

{
	name: "heading",
	match: "^!{1,6}",
	termRegExp: /(\n)/mg,
	handler: function(w)
	{
		w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
	}
},

{
	name: "list",
	match: "^(?:[\\*#;:]+)",
	lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
	termRegExp: /(\n)/mg,
	handler: function(w)
	{
		var stack = [w.output];
		var currLevel = 0, currType = null;
		var listLevel, listType, itemType, baseType;
		w.nextMatch = w.matchStart;
		this.lookaheadRegExp.lastIndex = w.nextMatch;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
			if(lookaheadMatch[1]) {
				listType = "ul";
				itemType = "li";
			} else if(lookaheadMatch[2]) {
				listType = "ol";
				itemType = "li";
			} else if(lookaheadMatch[3]) {
				listType = "dl";
				itemType = "dt";
			} else if(lookaheadMatch[4]) {
				listType = "dl";
				itemType = "dd";
			}
			if(!baseType)
				baseType = listType;
			listLevel = lookaheadMatch[0].length;
			w.nextMatch += lookaheadMatch[0].length;
			var t;
			if(listLevel > currLevel) {
				for(t=currLevel; t<listLevel; t++) {
					var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
					stack.push(createTiddlyElement(target,listType));
				}
			} else if(listType!=baseType && listLevel==1) {
				w.nextMatch -= lookaheadMatch[0].length;
				return;
			} else if(listLevel < currLevel) {
				for(t=currLevel; t>listLevel; t--)
					stack.pop();
			} else if(listLevel == currLevel && listType != currType) {
				stack.pop();
				stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
			}
			currLevel = listLevel;
			currType = listType;
			var e = createTiddlyElement(stack[stack.length-1],itemType);
			w.subWikifyTerm(e,this.termRegExp);
			this.lookaheadRegExp.lastIndex = w.nextMatch;
			lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		}
	}
},

{
	name: "quoteByBlock",
	match: "^<<<\\n",
	termRegExp: /(^<<<(\n|$))/mg,
	element: "blockquote",
	handler: config.formatterHelpers.createElementAndWikify
},

{
	name: "quoteByLine",
	match: "^>+",
	lookaheadRegExp: /^>+/mg,
	termRegExp: /(\n)/mg,
	element: "blockquote",
	handler: function(w)
	{
		var stack = [w.output];
		var currLevel = 0;
		var newLevel = w.matchLength;
		var t;
		do {
			if(newLevel > currLevel) {
				for(t=currLevel; t<newLevel; t++)
					stack.push(createTiddlyElement(stack[stack.length-1],this.element));
			} else if(newLevel < currLevel) {
				for(t=currLevel; t>newLevel; t--)
					stack.pop();
			}
			currLevel = newLevel;
			w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
			createTiddlyElement(stack[stack.length-1],"br");
			this.lookaheadRegExp.lastIndex = w.nextMatch;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
			if(matched) {
				newLevel = lookaheadMatch[0].length;
				w.nextMatch += lookaheadMatch[0].length;
			}
		} while(matched);
	}
},

{
	name: "rule",
	match: "^----+$\\n?",
	handler: function(w)
	{
		createTiddlyElement(w.output,"hr");
	}
},

{
	name: "monospacedByLine",
	match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
	element: "pre",
	handler: function(w)
	{
		switch(w.matchText) {
		case "/*{{{*/\n": // CSS
			this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
			break;
		case "{{{\n": // monospaced block
			this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
			break;
		case "//{{{\n": // plugin
			this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
			break;
		case "<!--{{{-->\n": //template
			this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
			break;
		default:
			break;
		}
		config.formatterHelpers.enclosedTextHelper.call(this,w);
	}
},

{
	name: "wikifyComment",
	match: "^(?:/\\*\\*\\*|<!---)\\n",
	handler: function(w)
	{
		var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
		w.subWikifyTerm(w.output,termRegExp);
	}
},

{
	name: "macro",
	match: "<<",
	lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
			w.nextMatch = this.lookaheadRegExp.lastIndex;
			invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
		}
	}
},

{
	name: "prettyLink",
	match: "\\[\\[",
	lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var e;
			var text = lookaheadMatch[1];
			if(lookaheadMatch[3]) {
				// Pretty bracketted link
				var link = lookaheadMatch[3];
				e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
						createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
			} else {
				// Simple bracketted link
				e = createTiddlyLink(w.output,text,false,null,w.isStatic,w.tiddler);
			}
			createTiddlyText(e,text);
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: "wikiLink",
	match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink,
	handler: function(w)
	{
		if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) {
			w.outputText(w.output,w.matchStart+1,w.nextMatch);
			return;
		}
		if(w.matchStart > 0) {
			var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
			preRegExp.lastIndex = w.matchStart-1;
			var preMatch = preRegExp.exec(w.source);
			if(preMatch.index == w.matchStart-1) {
				w.outputText(w.output,w.matchStart,w.nextMatch);
				return;
			}
		}
		if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
			var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
			w.outputText(link,w.matchStart,w.nextMatch);
		} else {
			w.outputText(w.output,w.matchStart,w.nextMatch);
		}
	}
},

{
	name: "urlLink",
	match: config.textPrimitives.urlPattern,
	handler: function(w)
	{
		w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
	}
},

{
	name: "image",
	match: "\\[[<>]?[Ii][Mm][Gg]\\[",
	lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var e = w.output;
			if(lookaheadMatch[5]) {
				var link = lookaheadMatch[5];
				e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
				addClass(e,"imageLink");
			}
			var img = createTiddlyElement(e,"img");
			if(lookaheadMatch[1])
				img.align = "left";
			else if(lookaheadMatch[2])
				img.align = "right";
			if(lookaheadMatch[3]) {
				img.title = lookaheadMatch[3];
				img.setAttribute("alt",lookaheadMatch[3]);
			}
			img.src = lookaheadMatch[4];
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: "html",
	match: "<[Hh][Tt][Mm][Ll]>",
	lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: "commentByBlock",
	match: "/%",
	lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
			w.nextMatch = this.lookaheadRegExp.lastIndex;
	}
},

{
	name: "characterFormat",
	match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
	handler: function(w)
	{
		switch(w.matchText) {
		case "''":
			w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg);
			break;
		case "//":
			w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg);
			break;
		case "__":
			w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg);
			break;
		case "^^":
			w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg);
			break;
		case "~~":
			w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg);
			break;
		case "--":
			w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg);
			break;
		case "{{{":
			var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
			lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
				createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
				w.nextMatch = lookaheadRegExp.lastIndex;
			}
			break;
		}
	}
},

{
	name: "customFormat",
	match: "@@|\\{\\{",
	handler: function(w)
	{
		switch(w.matchText) {
		case "@@":
			var e = createTiddlyElement(w.output,"span");
			var styles = config.formatterHelpers.inlineCssHelper(w);
			if(styles.length == 0)
				e.className = "marked";
			else
				config.formatterHelpers.applyCssHelper(e,styles);
			w.subWikifyTerm(e,/(@@)/mg);
			break;
		case "{{":
			lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
			lookaheadRegExp.lastIndex = w.matchStart;
			lookaheadMatch = lookaheadRegExp.exec(w.source);
			if(lookaheadMatch) {
				w.nextMatch = lookaheadRegExp.lastIndex;
				e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
				w.subWikifyTerm(e,/(\}\}\})/mg);
			}
			break;
		}
	}
},

{
	name: "mdash",
	match: "--",
	handler: function(w)
	{
		createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
	}
},

{
	name: "lineBreak",
	match: "\\n|<br ?/?>",
	handler: function(w)
	{
		createTiddlyElement(w.output,"br");
	}
},

{
	name: "rawText",
	match: "\\\"{3}|<nowiki>",
	lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: "htmlEntitiesEncoding",
	match: "(?:(?:&#?[a-zA-Z0-9]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[a-zA-Z0-9]{2,8};)",
	handler: function(w)
	{
		createTiddlyElement(w.output,"span").innerHTML = w.matchText;
	}
}

];

//--
//-- Wikifier
//--

function getParser(tiddler,format)
{
	if(tiddler) {
		if(!format)
			format = tiddler.fields["wikiformat"];
		var i;
		if(format) {
			for(i in config.parsers) {
				if(format == config.parsers[i].format)
					return config.parsers[i];
			}
		} else {
			for(i in config.parsers) {
				if(tiddler.isTagged(config.parsers[i].formatTag))
					return config.parsers[i];
			}
		}
	}
	return formatter;
}

function wikify(source,output,highlightRegExp,tiddler)
{
	if(source && source != "") {
		var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
		wikifier.subWikify(output);
	}
}

function wikifyStatic(source,highlightRegExp,tiddler,format)
{
	var e = createTiddlyElement(document.body,"pre");
	e.style.display = "none";
	var html = "";
	if(source && source != "") {
		if(!tiddler)
			tiddler = new Tiddler("temp");
		var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
		wikifier.isStatic = true;
		wikifier.subWikify(e);
		html = e.innerHTML;
		removeNode(e);
	}
	return html;
}

function wikifyPlain(title,theStore,limit)
{
	if(!theStore)
		theStore = store;
	if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
		return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
	} else {
		return "";
	}
}

function wikifyPlainText(text,limit,tiddler)
{
	if(limit > 0)
		text = text.substr(0,limit);
	var wikifier = new Wikifier(text,formatter,null,tiddler);
	return wikifier.wikifyPlain();
}

function highlightify(source,output,highlightRegExp,tiddler)
{
	if(source && source != "") {
		var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
		wikifier.outputText(output,0,source.length);
	}
}

function Wikifier(source,formatter,highlightRegExp,tiddler)
{
	this.source = source;
	this.output = null;
	this.formatter = formatter;
	this.nextMatch = 0;
	this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
	this.highlightRegExp = highlightRegExp;
	this.highlightMatch = null;
	this.isStatic = false;
	if(highlightRegExp) {
		highlightRegExp.lastIndex = 0;
		this.highlightMatch = highlightRegExp.exec(source);
	}
	this.tiddler = tiddler;
}

Wikifier.prototype.wikifyPlain = function()
{
	var e = createTiddlyElement(document.body,"div");
	e.style.display = "none";
	this.subWikify(e);
	var text = getPlainText(e);
	removeNode(e);
	return text;
};

Wikifier.prototype.subWikify = function(output,terminator)
{
	try {
		if(terminator)
			this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
		else
			this.subWikifyUnterm(output);
	} catch(ex) {
		showException(ex);
	}
};

Wikifier.prototype.subWikifyUnterm = function(output)
{
	var oldOutput = this.output;
	this.output = output;
	this.formatter.formatterRegExp.lastIndex = this.nextMatch;
	var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
	while(formatterMatch) {
		// Output any text before the match
		if(formatterMatch.index > this.nextMatch)
			this.outputText(this.output,this.nextMatch,formatterMatch.index);
		// Set the match parameters for the handler
		this.matchStart = formatterMatch.index;
		this.matchLength = formatterMatch[0].length;
		this.matchText = formatterMatch[0];
		this.nextMatch = this.formatter.formatterRegExp.lastIndex;
		for(var t=1; t<formatterMatch.length; t++) {
			if(formatterMatch[t]) {
				this.formatter.formatters[t-1].handler(this);
				this.formatter.formatterRegExp.lastIndex = this.nextMatch;
				break;
			}
		}
		formatterMatch = this.formatter.formatterRegExp.exec(this.source);
	}
	if(this.nextMatch < this.source.length) {
		this.outputText(this.output,this.nextMatch,this.source.length);
		this.nextMatch = this.source.length;
	}
	this.output = oldOutput;
};

Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
{
	var oldOutput = this.output;
	this.output = output;
	terminatorRegExp.lastIndex = this.nextMatch;
	var terminatorMatch = terminatorRegExp.exec(this.source);
	this.formatter.formatterRegExp.lastIndex = this.nextMatch;
	var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
	while(terminatorMatch || formatterMatch) {
		if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
			if(terminatorMatch.index > this.nextMatch)
				this.outputText(this.output,this.nextMatch,terminatorMatch.index);
			this.matchText = terminatorMatch[1];
			this.matchLength = terminatorMatch[1].length;
			this.matchStart = terminatorMatch.index;
			this.nextMatch = this.matchStart + this.matchLength;
			this.output = oldOutput;
			return;
		}
		if(formatterMatch.index > this.nextMatch)
			this.outputText(this.output,this.nextMatch,formatterMatch.index);
		this.matchStart = formatterMatch.index;
		this.matchLength = formatterMatch[0].length;
		this.matchText = formatterMatch[0];
		this.nextMatch = this.formatter.formatterRegExp.lastIndex;
		for(var t=1; t<formatterMatch.length; t++) {
			if(formatterMatch[t]) {
				this.formatter.formatters[t-1].handler(this);
				this.formatter.formatterRegExp.lastIndex = this.nextMatch;
				break;
			}
		}
		terminatorRegExp.lastIndex = this.nextMatch;
		terminatorMatch = terminatorRegExp.exec(this.source);
		formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
	}
	if(this.nextMatch < this.source.length) {
		this.outputText(this.output,this.nextMatch,this.source.length);
		this.nextMatch = this.source.length;
	}
	this.output = oldOutput;
};

Wikifier.prototype.outputText = function(place,startPos,endPos)
{
	while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
		if(this.highlightMatch.index > startPos) {
			createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
			startPos = this.highlightMatch.index;
		}
		var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
		var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
		startPos = highlightEnd;
		if(startPos >= this.highlightRegExp.lastIndex)
			this.highlightMatch = this.highlightRegExp.exec(this.source);
	}
	if(startPos < endPos) {
		createTiddlyText(place,this.source.substring(startPos,endPos));
	}
};

//--
//-- Macro definitions
//--

config.macros.today.handler = function(place,macroName,params)
{
	var now = new Date();
	var text = params[0] ? now.formatString(params[0].trim()) : now.toLocaleString();
	createTiddlyElement(place,"span",null,null,text);
};

config.macros.version.handler = function(place)
{
	createTiddlyElement(place,"span",null,null,formatVersion());
};

config.macros.list.handler = function(place,macroName,params)
{
	var type = params[0] ? params[0] : "all";
	var list = document.createElement("ul");
	place.appendChild(list);
	if(this[type].prompt)
		createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
	var results;
	if(this[type].handler)
		results = this[type].handler(params);
	for(var t = 0; t < results.length; t++) {
		var li = document.createElement("li");
		list.appendChild(li);
		createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
	}
};

config.macros.list.all.handler = function(params)
{
	return store.reverseLookup("tags","excludeLists",false,"title");
};

config.macros.list.missing.handler = function(params)
{
	return store.getMissingLinks();
};

config.macros.list.orphans.handler = function(params)
{
	return store.getOrphans();
};

config.macros.list.shadowed.handler = function(params)
{
	return store.getShadowed();
};

config.macros.list.touched.handler = function(params)
{
	return store.getTouched();
};

config.macros.list.filter.handler = function(params)
{
	var filter = params[1];
	var results = [];
	if(filter) {
		var tiddlers = store.filterTiddlers(filter);
		for(var t=0; t<tiddlers.length; t++)
			results.push(tiddlers[t].title);
	}
	return results;
};

config.macros.allTags.handler = function(place,macroName,params)
{
	var tags = store.getTags(params[0]);
	var ul = createTiddlyElement(place,"ul");
	if(tags.length == 0)
		createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
	for(var t=0; t<tags.length; t++) {
		var title = tags[t][0];
		var info = getTiddlyLinkInfo(title);
		var li = createTiddlyElement(ul,"li");
		var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
		btn.setAttribute("tag",title);
		btn.setAttribute("refresh","link");
		btn.setAttribute("tiddlyLink",title);
	}
};

config.macros.timeline.handler = function(place,macroName,params)
{
	var field = params[0] ? params[0] : "modified";
	var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
	var lastDay = "";
	var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
	var dateFormat = params[2] ? params[2] : this.dateFormat;
	for(var t=tiddlers.length-1; t>=last; t--) {
		var tiddler = tiddlers[t];
		var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
		if(theDay != lastDay) {
			var ul = document.createElement("ul");
			place.appendChild(ul);
			createTiddlyElement(ul,"li",null,"listTitle",tiddler[field].formatString(dateFormat));
			lastDay = theDay;
		}
		createTiddlyElement(ul,"li",null,"listLink").appendChild(createTiddlyLink(place,tiddler.title,true));
	}
};

config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	params = paramString.parseParams("name",null,true,false,true);
	var names = params[0]["name"];
	var tiddlerName = names[0];
	var className = names[1] ? names[1] : null;
	var args = params[0]["with"];
	var wrapper = createTiddlyElement(place,"span",null,className);
	if(!args) {
		wrapper.setAttribute("refresh","content");
		wrapper.setAttribute("tiddler",tiddlerName);
	}
	var text = store.getTiddlerText(tiddlerName);
	if(text) {
		var stack = config.macros.tiddler.tiddlerStack;
		if(stack.indexOf(tiddlerName) !== -1)
			return;
		stack.push(tiddlerName);
		try {
			var n = args ? Math.min(args.length,9) : 0;
			for(var i=0; i<n; i++) {
				var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
				text = text.replace(placeholderRE,args[i]);
			}
			config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
		} finally {
			stack.pop();
		}
	}
};

config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
{
	wikify(text,place,null,store.getTiddler(tiddlerName));
};

config.macros.tiddler.tiddlerStack = [];

config.macros.tag.handler = function(place,macroName,params)
{
	createTagButton(place,params[0],null,params[1],params[2]);
};

config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	params = paramString.parseParams("anon",null,true,false,false);
	var ul = createTiddlyElement(place,"ul");
	var title = getParam(params,"anon","");
	if(title && store.tiddlerExists(title))
		tiddler = store.getTiddler(title);
	var sep = getParam(params,"sep"," ");
	var lingo = config.views.wikified.tag;
	var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
	createTiddlyElement(ul,"li",null,"listTitle",prompt.format([tiddler.title]));
	for(var t=0; t<tiddler.tags.length; t++) {
		createTagButton(createTiddlyElement(ul,"li"),tiddler.tags[t],tiddler.title);
		if(t<tiddler.tags.length-1)
			createTiddlyText(ul,sep);
	}
};

config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	params = paramString.parseParams("anon",null,true,false,false);
	var ul = createTiddlyElement(place,"ul");
	var title = getParam(params,"anon","");
	if(title == "" && tiddler instanceof Tiddler)
		title = tiddler.title;
	var sep = getParam(params,"sep"," ");
	ul.setAttribute("title",this.tooltip.format([title]));
	var tagged = store.getTaggedTiddlers(title);
	var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
	createTiddlyElement(ul,"li",null,"listTitle",prompt.format([title,tagged.length]));
	for(var t=0; t<tagged.length; t++) {
		createTiddlyLink(createTiddlyElement(ul,"li"),tagged[t].title,true);
		if(t<tagged.length-1)
			createTiddlyText(ul,sep);
	}
};

config.macros.closeAll.handler = function(place)
{
	createTiddlyButton(place,this.label,this.prompt,this.onClick);
};

config.macros.closeAll.onClick = function(e)
{
	story.closeAllTiddlers();
	return false;
};

config.macros.permaview.handler = function(place)
{
	createTiddlyButton(place,this.label,this.prompt,this.onClick);
};

config.macros.permaview.onClick = function(e)
{
	story.permaView();
	return false;
};

config.macros.saveChanges.handler = function(place,macroName,params)
{
	if(!readOnly)
		createTiddlyButton(place,params[0] || this.label,params[1] || this.prompt,this.onClick,null,null,this.accessKey);
};

config.macros.saveChanges.onClick = function(e)
{
	saveChanges();
	return false;
};

config.macros.slider.onClickSlider = function(ev)
{
	var e = ev ? ev : window.event;
	var n = this.nextSibling;
	var cookie = n.getAttribute("cookie");
	var isOpen = n.style.display != "none";
	if(config.options.chkAnimate && anim && typeof Slider == "function")
		anim.startAnimating(new Slider(n,!isOpen,null,"none"));
	else
		n.style.display = isOpen ? "none" : "block";
	config.options[cookie] = !isOpen;
	saveOptionCookie(cookie);
	return false;
};

config.macros.slider.createSlider = function(place,cookie,title,tooltip)
{
	var c = cookie ? cookie : "";
	var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
	var panel = createTiddlyElement(null,"div",null,"sliderPanel");
	panel.setAttribute("cookie",c);
	panel.style.display = config.options[c] ? "block" : "none";
	place.appendChild(panel);
	return panel;
};

config.macros.slider.handler = function(place,macroName,params)
{
	var panel = this.createSlider(place,params[0],params[2],params[3]);
	var text = store.getTiddlerText(params[1]);
	panel.setAttribute("refresh","content");
	panel.setAttribute("tiddler",params[1]);
	if(text)
		wikify(text,panel,null,store.getTiddler(params[1]));
};

// <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
config.macros.gradient.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	var panel = wikifier ? createTiddlyElement(place,"div",null,"gradient") : place;
	panel.style.position = "relative";
	panel.style.overflow = "hidden";
	panel.style.zIndex = "0";
	if(wikifier) {
		var styles = config.formatterHelpers.inlineCssHelper(wikifier);
		config.formatterHelpers.applyCssHelper(panel,styles);
	}
	params = paramString.parseParams("color");
	var locolors = [], hicolors = [];
	for(var t=2; t<params.length; t++) {
		var c = new RGB(params[t].value);
		if(params[t].name == "snap") {
			hicolors[hicolors.length-1] = c;
		} else {
			locolors.push(c);
			hicolors.push(c);
		}
	}
	drawGradient(panel,params[1].value != "vert",locolors,hicolors);
	if(wikifier)
		wikifier.subWikify(panel,">>");
	if(document.all) {
		panel.style.height = "100%";
		panel.style.width = "100%";
	}
};

config.macros.message.handler = function(place,macroName,params)
{
	if(params[0]) {
		var names = params[0].split(".");
		var lookupMessage = function(root,nameIndex) {
				if(names[nameIndex] in root) {
					if(nameIndex < names.length-1)
						return (lookupMessage(root[names[nameIndex]],nameIndex+1));
					else
						return root[names[nameIndex]];
				} else
					return null;
			};
		var m = lookupMessage(config,0);
		if(m == null)
			m = lookupMessage(window,0);
		createTiddlyText(place,m.toString().format(params.splice(1)));
	}
};


config.macros.view.views = {
	text: function(value,place,params,wikifier,paramString,tiddler) {
		highlightify(value,place,highlightHack,tiddler);
	},
	link: function(value,place,params,wikifier,paramString,tiddler) {
		createTiddlyLink(place,value,true);
	},
	wikified: function(value,place,params,wikifier,paramString,tiddler) {
		if(params[2])
			value=params[2].unescapeLineBreaks().format([value]);
		wikify(value,place,highlightHack,tiddler);
	},
	date: function(value,place,params,wikifier,paramString,tiddler) {
		value = Date.convertFromYYYYMMDDHHMM(value);
		createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
	}
};

config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	if((tiddler instanceof Tiddler) && params[0]) {
		var value = store.getValue(tiddler,params[0]);
		if(value) {
			var type = params[1] ? params[1] : config.macros.view.defaultView;
			var handler = config.macros.view.views[type];
			if(handler)
				handler(value,place,params,wikifier,paramString,tiddler);
		}
	}
};

config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	var field = params[0];
	var rows = params[1] || 0;
	var defVal = params[2] || '';
	if((tiddler instanceof Tiddler) && field) {
		story.setDirty(tiddler.title,true);
		var e,v;
		if(field != "text" && !rows) {
			e = createTiddlyElement(null,"input");
			if(tiddler.isReadOnly())
				e.setAttribute("readOnly","readOnly");
			e.setAttribute("edit",field);
			e.setAttribute("type","text");
			e.value = store.getValue(tiddler,field) || defVal;
			e.setAttribute("size","40");
			e.setAttribute("autocomplete","off");
			place.appendChild(e);
		} else {
			var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
			var wrapper2 = createTiddlyElement(wrapper1,"div");
			e = createTiddlyElement(wrapper2,"textarea");
			if(tiddler.isReadOnly())
				e.setAttribute("readOnly","readOnly");
			e.value = v = store.getValue(tiddler,field) || defVal;
			rows = rows ? rows : 10;
			var lines = v.match(/\n/mg);
			var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
			if(lines != null && lines.length > rows)
				rows = lines.length + 5;
			rows = Math.min(rows,maxLines);
			e.setAttribute("rows",rows);
			e.setAttribute("edit",field);
			place.appendChild(wrapper1);
		}
		return e;
	}
};

config.macros.tagChooser.onClick = function(ev)
{
	var e = ev ? ev : window.event;
	if(e.metaKey || e.ctrlKey) stopEvent(e); //# keep popup open on CTRL-click
	var lingo = config.views.editor.tagChooser;
	var popup = Popup.create(this);
	var tags = store.getTags();
	if(tags.length == 0)
		createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
	for(var t=0; t<tags.length; t++) {
		var tag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
		tag.setAttribute("tag",tags[t][0]);
		tag.setAttribute("tiddler",this.getAttribute("tiddler"));
	}
	Popup.show();
	e.cancelBubble = true;
	if(e.stopPropagation) e.stopPropagation();
	return false;
};

config.macros.tagChooser.onTagClick = function(ev)
{
	var e = ev ? ev : window.event;
	var tag = this.getAttribute("tag");
	var title = this.getAttribute("tiddler");
	if(!readOnly)
		story.setTiddlerTag(title,tag,0);
	return false;
};

config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	if(tiddler instanceof Tiddler) {
		var lingo = config.views.editor.tagChooser;
		var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
		btn.setAttribute("tiddler",tiddler.title);
	}
};

config.macros.refreshDisplay.handler = function(place)
{
	createTiddlyButton(place,this.label,this.prompt,this.onClick);
};

config.macros.refreshDisplay.onClick = function(e)
{
	refreshAll();
	return false;
};

config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	var title = tiddler ? tiddler.title : null;
	var a = title ? config.annotations[title] : null;
	if(!tiddler || !title || !a)
		return;
	var text = a.format([title]);
	wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
};

//--
//-- NewTiddler and NewJournal macros
//--

config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
{
	var tags = [];
	for(var t=1; t<params.length; t++) {
		if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
			tags.push(params[t].value);
	}
	label = getParam(params,"label",label);
	prompt = getParam(params,"prompt",prompt);
	accessKey = getParam(params,"accessKey",accessKey);
	newFocus = getParam(params,"focus",newFocus);
	var customFields = getParam(params,"fields","");
	if(!customFields && !store.isShadowTiddler(title))
		customFields = String.encodeHashMap(config.defaultCustomFields);
	var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
	btn.setAttribute("newTitle",title);
	btn.setAttribute("isJournal",isJournal ? "true" : "false");
	if(tags.length > 0)
		btn.setAttribute("params",tags.join("|"));
	btn.setAttribute("newFocus",newFocus);
	btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
	if(customFields !== "")
		btn.setAttribute("customFields",customFields);
	var text = getParam(params,"text");
	if(text !== undefined)
		btn.setAttribute("newText",text);
	return btn;
};

config.macros.newTiddler.onClickNewTiddler = function()
{
	var title = this.getAttribute("newTitle");
	if(this.getAttribute("isJournal") == "true") {
		var now = new Date();
		title = now.formatString(title.trim());
	}
	var params = this.getAttribute("params");
	var tags = params ? params.split("|") : [];
	var focus = this.getAttribute("newFocus");
	var template = this.getAttribute("newTemplate");
	var customFields = this.getAttribute("customFields");
	story.displayTiddler(null,title,template,false,null,null);
	var tiddlerElem = story.getTiddler(title);
	if(customFields)
		story.addCustomFields(tiddlerElem,customFields);
	var text = this.getAttribute("newText");
	if(typeof text == "string")
		story.getTiddlerField(title,"text").value = text.format([title]);
	for(var t=0;t<tags.length;t++)
		story.setTiddlerTag(title,tags[t],+1);
	story.focusTiddler(title,focus);
	return false;
};

config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	if(!readOnly) {
		params = paramString.parseParams("anon",null,true,false,false);
		var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
		title = getParam(params,"title",title);
		this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
	}
};

config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	if(!readOnly) {
		params = paramString.parseParams("anon",null,true,false,false);
		var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
		title = getParam(params,"title",title);
		config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
	}
};

//--
//-- Search macro
//--

config.macros.search.handler = function(place,macroName,params)
{
	var searchTimeout = null;
	var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick,"searchButton");
	var txt = createTiddlyElement(place,"input",null,"txtOptionInput searchField");
	if(params[0])
		txt.value = params[0];
	txt.onkeyup = this.onKeyPress;
	txt.onfocus = this.onFocus;
	txt.setAttribute("size",this.sizeTextbox);
	txt.setAttribute("accessKey",this.accessKey);
	txt.setAttribute("autocomplete","off");
	txt.setAttribute("lastSearchText","");
	if(config.browser.isSafari) {
		txt.setAttribute("type","search");
		txt.setAttribute("results","5");
	} else {
		txt.setAttribute("type","text");
	}
};

// Global because there's only ever one outstanding incremental search timer
config.macros.search.timeout = null;

config.macros.search.doSearch = function(txt)
{
	if(txt.value.length > 0) {
		story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
		txt.setAttribute("lastSearchText",txt.value);
	}
};

config.macros.search.onClick = function(e)
{
	config.macros.search.doSearch(this.nextSibling);
	return false;
};

config.macros.search.onKeyPress = function(ev)
{
	var e = ev ? ev : window.event;
	switch(e.keyCode) {
		case 13: // Ctrl-Enter
		case 10: // Ctrl-Enter on IE PC
			config.macros.search.doSearch(this);
			break;
		case 27: // Escape
			this.value = "";
			clearMessage();
			break;
	}
	if(config.options.chkIncrementalSearch) {
		if(this.value.length > 2) {
			if(this.value != this.getAttribute("lastSearchText")) {
				if(config.macros.search.timeout)
					clearTimeout(config.macros.search.timeout);
				var txt = this;
				config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
			}
		} else {
			if(config.macros.search.timeout)
				clearTimeout(config.macros.search.timeout);
		}
	}
};

config.macros.search.onFocus = function(e)
{
	this.select();
};

//--
//-- Tabs macro
//--

config.macros.tabs.handler = function(place,macroName,params)
{
	var cookie = params[0];
	var numTabs = (params.length-1)/3;
	var wrapper = createTiddlyElement(null,"div",null,"tabsetWrapper " + cookie);
	var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
	tabset.setAttribute("cookie",cookie);
	var validTab = false;
	for(var t=0; t<numTabs; t++) {
		var label = params[t*3+1];
		var prompt = params[t*3+2];
		var content = params[t*3+3];
		var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
		tab.setAttribute("tab",label);
		tab.setAttribute("content",content);
		tab.title = prompt;
		if(config.options[cookie] == label)
			validTab = true;
	}
	if(!validTab)
		config.options[cookie] = params[1];
	place.appendChild(wrapper);
	this.switchTab(tabset,config.options[cookie]);
};

config.macros.tabs.onClickTab = function(e)
{
	config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
	return false;
};

config.macros.tabs.switchTab = function(tabset,tab)
{
	var cookie = tabset.getAttribute("cookie");
	var theTab = null;
	var nodes = tabset.childNodes;
	for(var t=0; t<nodes.length; t++) {
		if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
			theTab = nodes[t];
			theTab.className = "tab tabSelected";
		} else {
			nodes[t].className = "tab tabUnselected";
		}
	}
	if(theTab) {
		if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
			removeNode(tabset.nextSibling);
		var tabContent = createTiddlyElement(null,"div",null,"tabContents");
		tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
		var contentTitle = theTab.getAttribute("content");
		wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
		if(cookie) {
			config.options[cookie] = tab;
			saveOptionCookie(cookie);
		}
	}
};

//--
//-- Tiddler toolbar
//--

// Create a toolbar command button
config.macros.toolbar.createCommand = function(place,commandName,tiddler,className)
{
	if(typeof commandName != "string") {
		var c = null;
		for(var t in config.commands) {
			if(config.commands[t] == commandName)
				c = t;
		}
		commandName = c;
	}
	if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
		var command = config.commands[commandName];
		if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
			var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
			var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
			var cmd;
			switch(command.type) {
				case "popup":
					cmd = this.onClickPopup;
					break;
				case "command":
				default:
					cmd = this.onClickCommand;
					break;
			}
			var btn = createTiddlyButton(null,text,tooltip,cmd);
			btn.setAttribute("commandName",commandName);
			btn.setAttribute("tiddler",tiddler.title);
			if(className)
				addClass(btn,className);
			place.appendChild(btn);
		}
	}
};

config.macros.toolbar.isCommandEnabled = function(command,tiddler)
{
	var title = tiddler.title;
	var ro = tiddler.isReadOnly();
	var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
	return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
};

config.macros.toolbar.getCommandText = function(command,tiddler)
{
	return tiddler.isReadOnly() && command.readOnlyText ? command.readOnlyText : command.text;
};

config.macros.toolbar.getCommandTooltip = function(command,tiddler)
{
	return tiddler.isReadOnly() && command.readOnlyTooltip ? command.readOnlyTooltip : command.tooltip;
};

config.macros.toolbar.onClickCommand = function(ev)
{
	var e = ev ? ev : window.event;
	e.cancelBubble = true;
	if(e.stopPropagation) e.stopPropagation();
	var command = config.commands[this.getAttribute("commandName")];
	return command.handler(e,this,this.getAttribute("tiddler"));
};

config.macros.toolbar.onClickPopup = function(ev)
{
	var e = ev ? ev : window.event;
	e.cancelBubble = true;
	if(e.stopPropagation) e.stopPropagation();
	var popup = Popup.create(this);
	var command = config.commands[this.getAttribute("commandName")];
	var title = this.getAttribute("tiddler");
	var tiddler = store.fetchTiddler(title);
	popup.setAttribute("tiddler",title);
	command.handlePopup(popup,title);
	Popup.show();
	return false;
};

// Invoke the first command encountered from a given place that is tagged with a specified class
config.macros.toolbar.invokeCommand = function(place,className,event)
{
	var children = place.getElementsByTagName("a");
	for(var t=0; t<children.length; t++) {
		var c = children[t];
		if(hasClass(c,className) && c.getAttribute && c.getAttribute("commandName")) {
			if(c.onclick instanceof Function)
				c.onclick.call(c,event);
			break;
		}
	}
};

config.macros.toolbar.onClickMore = function(ev)
{
	var e = this.nextSibling;
	e.style.display = "inline";
	removeNode(this);
	return false;
};

config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	for(var t=0; t<params.length; t++) {
		var c = params[t];
		switch(c) {
			case '>':
				var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
				addClass(btn,"moreCommand");
				var e = createTiddlyElement(place,"span",null,"moreCommand");
				e.style.display = "none";
				place = e;
				break;
			default:
				var className = "";
				switch(c.substr(0,1)) {
					case "+":
						className = "defaultCommand";
						c = c.substr(1);
						break;
					case "-":
						className = "cancelCommand";
						c = c.substr(1);
						break;
				}
				if(c in config.commands)
					this.createCommand(place,c,tiddler,className);
				break;
		}
	}
};

//--
//-- Menu and toolbar commands
//--

config.commands.closeTiddler.handler = function(event,src,title)
{
	if(story.isDirty(title) && !readOnly) {
		if(!confirm(config.commands.cancelTiddler.warning.format([title])))
			return false;
	}
	story.setDirty(title,false);
	story.closeTiddler(title,true);
	return false;
};

config.commands.closeOthers.handler = function(event,src,title)
{
	story.closeAllTiddlers(title);
	return false;
};

config.commands.editTiddler.handler = function(event,src,title)
{
	clearMessage();
	var tiddlerElem = story.getTiddler(title);
	var fields = tiddlerElem.getAttribute("tiddlyFields");
	story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
	story.focusTiddler(title,config.options.txtEditorFocus||"text");
	return false;
};

config.commands.saveTiddler.handler = function(event,src,title)
{
	var newTitle = story.saveTiddler(title,event.shiftKey);
	if(newTitle)
		story.displayTiddler(null,newTitle);
	return false;
};

config.commands.cancelTiddler.handler = function(event,src,title)
{
	if(story.hasChanges(title) && !readOnly) {
		if(!confirm(this.warning.format([title])))
			return false;
	}
	story.setDirty(title,false);
	story.displayTiddler(null,title);
	return false;
};

config.commands.deleteTiddler.handler = function(event,src,title)
{
	var deleteIt = true;
	if(config.options.chkConfirmDelete)
		deleteIt = confirm(this.warning.format([title]));
	if(deleteIt) {
		store.removeTiddler(title);
		story.closeTiddler(title,true);
		autoSaveChanges();
	}
	return false;
};

config.commands.permalink.handler = function(event,src,title)
{
	var t = encodeURIComponent(String.encodeTiddlyLink(title));
	if(window.location.hash != t)
		window.location.hash = t;
	return false;
};

config.commands.references.handlePopup = function(popup,title)
{
	var references = store.getReferringTiddlers(title);
	var c = false;
	for(var r=0; r<references.length; r++) {
		if(references[r].title != title && !references[r].isTagged("excludeLists")) {
			createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
			c = true;
		}
	}
	if(!c)
		createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
};

config.commands.jump.handlePopup = function(popup,title)
{
	story.forEachTiddler(function(title,element) {
		createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
		});
};

config.commands.syncing.handlePopup = function(popup,title)
{
	var tiddler = store.fetchTiddler(title);
	if(!tiddler)
		return;
	var serverType = tiddler.getServerType();
	var serverHost = tiddler.fields['server.host'];
	var serverWorkspace = tiddler.fields['server.workspace'];
	if(!serverWorkspace)
		serverWorkspace = "";
	if(serverType) {
		var e = createTiddlyElement(popup,"li",null,"popupMessage");
		e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
	} else {
		createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
	}
	if(serverType) {
		createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
		var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
		btn.setAttribute("tiddler",title);
		btn.setAttribute("server.type","");
	}
	createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
	createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
	var feeds = store.getTaggedTiddlers("systemServer","title");
	for(var t=0; t<feeds.length; t++) {
		var f = feeds[t];
		var feedServerType = store.getTiddlerSlice(f.title,"Type");
		if(!feedServerType)
			feedServerType = "file";
		var feedServerHost = store.getTiddlerSlice(f.title,"URL");
		if(!feedServerHost)
			feedServerHost = "";
		var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
		if(!feedServerWorkspace)
			feedServerWorkspace = "";
		var caption = f.title;
		if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
			caption = config.commands.syncing.currServerMarker + caption;
		} else {
			caption = config.commands.syncing.notCurrServerMarker + caption;
		}
		btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
		btn.setAttribute("tiddler",title);
		btn.setAttribute("server.type",feedServerType);
		btn.setAttribute("server.host",feedServerHost);
		btn.setAttribute("server.workspace",feedServerWorkspace);
	}
};

config.commands.syncing.onChooseServer = function(e)
{
	var tiddler = this.getAttribute("tiddler");
	var serverType = this.getAttribute("server.type");
	if(serverType) {
		store.addTiddlerFields(tiddler,{
			'server.type': serverType,
			'server.host': this.getAttribute("server.host"),
			'server.workspace': this.getAttribute("server.workspace")
			});
	} else {
		store.setValue(tiddler,'server',null);
	}
	return false;
};

config.commands.fields.handlePopup = function(popup,title)
{
	var tiddler = store.fetchTiddler(title);
	if(!tiddler)
		return;
	var fields = {};
	store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
	var items = [];
	for(var t in fields) {
		items.push({field: t,value: fields[t]});
	}
	items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
	if(items.length > 0)
		ListView.create(popup,items,this.listViewTemplate);
	else
		createTiddlyElement(popup,"div",null,null,this.emptyText);
};

//--
//-- Tiddler() object
//--

function Tiddler(title)
{
	this.title = title;
	this.text = "";
	this.modifier = null;
	this.created = new Date();
	this.modified = this.created;
	this.links = [];
	this.linksUpdated = false;
	this.tags = [];
	this.fields = {};
	return this;
}

Tiddler.prototype.getLinks = function()
{
	if(this.linksUpdated==false)
		this.changed();
	return this.links;
};

// Returns the fields that are inherited in string field:"value" field2:"value2" format
Tiddler.prototype.getInheritedFields = function()
{
	var f = {};
	for(i in this.fields) {
		if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
			f[i] = this.fields[i];
		}
	}
	return String.encodeHashMap(f);
};

// Increment the changeCount of a tiddler
Tiddler.prototype.incChangeCount = function()
{
	var c = this.fields['changecount'];
	c = c ? parseInt(c) : 0;
	this.fields['changecount'] = String(c+1);
};

// Clear the changeCount of a tiddler
Tiddler.prototype.clearChangeCount = function()
{
	if(this.fields['changecount']) {
		delete this.fields['changecount'];
	}
};

Tiddler.prototype.doNotSave = function()
{
	return this.fields['doNotSave'];
};

// Returns true if the tiddler has been updated since the tiddler was created or downloaded
Tiddler.prototype.isTouched = function()
{
	var changeCount = this.fields['changecount'];
	if(changeCount === undefined)
		changeCount = 0;
	return changeCount > 0;
};

// Return the tiddler as an RSS item
Tiddler.prototype.toRssItem = function(uri)
{
	var s = [];
	s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
	s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
	for(var t=0; t<this.tags.length; t++)
		s.push("<category>" + this.tags[t] + "</category>");
	s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
	s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
	return s.join("\n");
};

// Format the text for storage in an RSS item
Tiddler.prototype.saveToRss = function(uri)
{
	return "<item>\n" + this.toRssItem(uri) + "\n</item>";
};

// Change the text and other attributes of a tiddler
Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
{
	this.assign(title,text,modifier,modified,tags,created,fields);
	this.changed();
	return this;
};

// Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
{
	if(title != undefined)
		this.title = title;
	if(text != undefined)
		this.text = text;
	if(modifier != undefined)
		this.modifier = modifier;
	if(modified != undefined)
		this.modified = modified;
	if(created != undefined)
		this.created = created;
	if(fields != undefined)
		this.fields = fields;
	if(tags != undefined)
		this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
	else if(this.tags == undefined)
		this.tags = [];
	return this;
};

// Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
Tiddler.prototype.getTags = function()
{
	return String.encodeTiddlyLinkList(this.tags);
};

// Test if a tiddler carries a tag
Tiddler.prototype.isTagged = function(tag)
{
	return this.tags.indexOf(tag) != -1;
};

// Static method to convert "\n" to newlines, "\s" to "\"
Tiddler.unescapeLineBreaks = function(text)
{
	return text ? text.unescapeLineBreaks() : "";
};

// Convert newlines to "\n", "\" to "\s"
Tiddler.prototype.escapeLineBreaks = function()
{
	return this.text.escapeLineBreaks();
};

// Updates the secondary information (like links[] array) after a change to a tiddler
Tiddler.prototype.changed = function()
{
	this.links = [];
	var t = this.autoLinkWikiWords() ? 0 : 1;
	var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
	tiddlerLinkRegExp.lastIndex = 0;
	var formatMatch = tiddlerLinkRegExp.exec(this.text);
	while(formatMatch) {
		var lastIndex = tiddlerLinkRegExp.lastIndex;
		if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
			// wikiWordLink
			if(formatMatch.index > 0) {
				var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
				preRegExp.lastIndex = formatMatch.index-1;
				var preMatch = preRegExp.exec(this.text);
				if(preMatch.index != formatMatch.index-1)
					this.links.pushUnique(formatMatch[1]);
			} else {
				this.links.pushUnique(formatMatch[1]);
			}
		}
		else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
			this.links.pushUnique(formatMatch[3-t]);
		else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
			this.links.pushUnique(formatMatch[4-t]);
		tiddlerLinkRegExp.lastIndex = lastIndex;
		formatMatch = tiddlerLinkRegExp.exec(this.text);
	}
	this.linksUpdated = true;
};

Tiddler.prototype.getSubtitle = function()
{
	var modifier = this.modifier;
	if(!modifier)
		modifier = config.messages.subtitleUnknown;
	var modified = this.modified;
	if(modified)
		modified = modified.toLocaleString();
	else
		modified = config.messages.subtitleUnknown;
	return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
};

Tiddler.prototype.isReadOnly = function()
{
	return readOnly;
};

Tiddler.prototype.autoLinkWikiWords = function()
{
	return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
};

Tiddler.prototype.generateFingerprint = function()
{
	return "0x" + Crypto.hexSha1Str(this.text);
};

Tiddler.prototype.getServerType = function()
{
	var serverType = null;
	if(this.fields && this.fields['server.type'])
		serverType = this.fields['server.type'];
	if(!serverType)
		serverType = this.fields['wikiformat'];
	if(serverType && !config.adaptors[serverType])
		serverType = null;
	return serverType;
};

Tiddler.prototype.getAdaptor = function()
{
	var serverType = this.getServerType();
	return serverType ? new config.adaptors[serverType] : null;
};

//--
//-- TiddlyWiki() object contains Tiddler()s
//--

function TiddlyWiki()
{
	var tiddlers = {}; // Hashmap by name of tiddlers
	this.tiddlersUpdated = false;
	this.namedNotifications = []; // Array of {name:,notify:} of notification functions
	this.notificationLevel = 0;
	this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
	this.clear = function() {
		tiddlers = {};
		this.setDirty(false);
	};
	this.fetchTiddler = function(title) {
		var t = tiddlers[title];
		return t instanceof Tiddler ? t : null;
	};
	this.deleteTiddler = function(title) {
		delete this.slices[title];
		delete tiddlers[title];
	};
	this.addTiddler = function(tiddler) {
		delete this.slices[tiddler.title];
		tiddlers[tiddler.title] = tiddler;
	};
	this.forEachTiddler = function(callback) {
		for(var t in tiddlers) {
			var tiddler = tiddlers[t];
			if(tiddler instanceof Tiddler)
				callback.call(this,t,tiddler);
		}
	};
}

TiddlyWiki.prototype.setDirty = function(dirty)
{
	this.dirty = dirty;
};

TiddlyWiki.prototype.isDirty = function()
{
	return this.dirty;
};

TiddlyWiki.prototype.suspendNotifications = function()
{
	this.notificationLevel--;
};

TiddlyWiki.prototype.resumeNotifications = function()
{
	this.notificationLevel++;
};

// Invoke the notification handlers for a particular tiddler
TiddlyWiki.prototype.notify = function(title,doBlanket)
{
	if(!this.notificationLevel) {
		for(var t=0; t<this.namedNotifications.length; t++) {
			var n = this.namedNotifications[t];
			if((n.name == null && doBlanket) || (n.name == title))
				n.notify(title);
		}
	}
};

// Invoke the notification handlers for all tiddlers
TiddlyWiki.prototype.notifyAll = function()
{
	if(!this.notificationLevel) {
		for(var t=0; t<this.namedNotifications.length; t++) {
			var n = this.namedNotifications[t];
			if(n.name)
				n.notify(n.name);
		}
	}
};

// Add a notification handler to a tiddler
TiddlyWiki.prototype.addNotification = function(title,fn)
{
	for(var i=0; i<this.namedNotifications.length; i++) {
		if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
			return this;
	}
	this.namedNotifications.push({name: title, notify: fn});
	return this;
};

TiddlyWiki.prototype.removeTiddler = function(title)
{
	var tiddler = this.fetchTiddler(title);
	if(tiddler) {
		this.deleteTiddler(title);
		this.notify(title,true);
		this.setDirty(true);
	}
};

TiddlyWiki.prototype.tiddlerExists = function(title)
{
	var t = this.fetchTiddler(title);
	return t != undefined;
};

TiddlyWiki.prototype.isShadowTiddler = function(title)
{
	return typeof config.shadowTiddlers[title] == "string";
};

TiddlyWiki.prototype.getTiddler = function(title)
{
	var t = this.fetchTiddler(title);
	if(t != undefined)
		return t;
	else
		return null;
};

TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
{
	if(!title)
		return defaultText;
	var pos = title.indexOf(config.textPrimitives.sectionSeparator);
	var section = null;
	if(pos != -1) {
		section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
		title = title.substr(0,pos);
	}
	pos = title.indexOf(config.textPrimitives.sliceSeparator);
	if(pos != -1) {
		var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
		if(slice)
			return slice;
	}
	var tiddler = this.fetchTiddler(title);
	if(tiddler) {
		if(!section)
			return tiddler.text;
		var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + "[ \t]*\n)","mg");
		re.lastIndex = 0;
		var match =  re.exec(tiddler.text);
		if(match) {
			var t = tiddler.text.substr(match.index+match[1].length);
			var re2 = /^!/mg;
			re2.lastIndex = 0;
			match = re2.exec(t); //# search for the next heading
			if(match)
				t = t.substr(0,match.index-1);//# don't include final \n
			return t;
		}
		return defaultText;
	}
	if(this.isShadowTiddler(title))
		return config.shadowTiddlers[title];
	if(defaultText != undefined)
		return defaultText;
	return null;
};

TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?([\.\w]+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?([\.\w]+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm;

// @internal
TiddlyWiki.prototype.calcAllSlices = function(title)
{
	var slices = {};
	var text = this.getTiddlerText(title,"");
	this.slicesRE.lastIndex = 0;
	do {
		var m = this.slicesRE.exec(text);
		if(m) {
			if(m[1])
				slices[m[1]] = m[2];
			else
				slices[m[3]] = m[4];
		}
	} while(m);
	return slices;
};

// Returns the slice of text of the given name
TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
{
	var slices = this.slices[title];
	if(!slices) {
		slices = this.calcAllSlices(title);
		this.slices[title] = slices;
	}
	return slices[sliceName];
};

// Build an hashmap of the specified named slices of a tiddler
TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
{
	var r = {};
	for(var t=0; t<sliceNames.length; t++) {
		var slice = this.getTiddlerSlice(title,sliceNames[t]);
		if(slice)
			r[sliceNames[t]] = slice;
	}
	return r;
};

TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
{
	var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
	var text = this.getTiddlerText(title,null);
	if(text == null)
		return defaultText;
	var textOut = [];
	var lastPos = 0;
	do {
		var match = bracketRegExp.exec(text);
		if(match) {
			textOut.push(text.substr(lastPos,match.index-lastPos));
			if(match[1]) {
				if(depth <= 0)
					textOut.push(match[1]);
				else
					textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
			}
			lastPos = match.index + match[0].length;
		} else {
			textOut.push(text.substr(lastPos));
		}
	} while(match);
	return textOut.join("");
};

TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
{
	var tiddler = this.fetchTiddler(title);
	if(tiddler) {
		var t = tiddler.tags.indexOf(tag);
		if(t != -1)
			tiddler.tags.splice(t,1);
		if(status)
			tiddler.tags.push(tag);
		tiddler.changed();
		this.incChangeCount(title);
		this.notify(title,true);
		this.setDirty(true);
	}
};

TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
{
	var tiddler = this.fetchTiddler(title);
	if(!tiddler)
		return;
	merge(tiddler.fields,fields);
	tiddler.changed();
	this.incChangeCount(title);
	this.notify(title,true);
	this.setDirty(true);
};

TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
{
	var tiddler = this.fetchTiddler(title);
	if(tiddler) {
		created = created ? created : tiddler.created; // Preserve created date
		this.deleteTiddler(title);
	} else {
		created = created ? created : modified;
		tiddler = new Tiddler();
	}
	tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
	this.addTiddler(tiddler);
	if(clearChangeCount)
		tiddler.clearChangeCount();
	else
		tiddler.incChangeCount();
	if(title != newTitle)
		this.notify(title,true);
	this.notify(newTitle,true);
	this.setDirty(true);
	return tiddler;
};

// Reset the sync status of a freshly synced tiddler
TiddlyWiki.prototype.resetTiddler = function(title)
{
	var tiddler = this.fetchTiddler(title);
	if(tiddler) {
		tiddler.clearChangeCount();
		this.notify(title,true);
		this.setDirty(true);
	}
};

TiddlyWiki.prototype.incChangeCount = function(title)
{
	var tiddler = this.fetchTiddler(title);
	if(tiddler)
		tiddler.incChangeCount();
};

TiddlyWiki.prototype.createTiddler = function(title)
{
	var tiddler = this.fetchTiddler(title);
	if(!tiddler) {
		tiddler = new Tiddler(title);
		this.addTiddler(tiddler);
		this.setDirty(true);
	}
	return tiddler;
};

// Load contents of a TiddlyWiki from an HTML DIV
TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
{
	this.idPrefix = idPrefix;
	var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
	if(!storeElem)
		return;
	var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
	this.setDirty(false);
	if(!noUpdate) {
		for(var i = 0;i<tiddlers.length; i++)
			tiddlers[i].changed();
	}
};

// Load contents of a TiddlyWiki from a string
// Returns null if there's an error
TiddlyWiki.prototype.importTiddlyWiki = function(text)
{
	var posDiv = locateStoreArea(text);
	if(!posDiv)
		return null;
	var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
	// Create the iframe
	var iframe = document.createElement("iframe");
	iframe.style.display = "none";
	document.body.appendChild(iframe);
	var doc = iframe.document;
	if(iframe.contentDocument)
		doc = iframe.contentDocument; // For NS6
	else if(iframe.contentWindow)
		doc = iframe.contentWindow.document; // For IE5.5 and IE6
	// Put the content in the iframe
	doc.open();
	doc.writeln(content);
	doc.close();
	// Load the content into a TiddlyWiki() object
	var storeArea = doc.getElementById("storeArea");
	this.loadFromDiv(storeArea,"store");
	// Get rid of the iframe
	iframe.parentNode.removeChild(iframe);
	return this;
};

TiddlyWiki.prototype.updateTiddlers = function()
{
	this.tiddlersUpdated = true;
	this.forEachTiddler(function(title,tiddler) {
		tiddler.changed();
	});
};

// Return all tiddlers formatted as an HTML string
TiddlyWiki.prototype.allTiddlersAsHtml = function()
{
	return store.getSaver().externalize(store);
};

// Return an array of tiddlers matching a search regular expression
TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
{
	var candidates = this.reverseLookup("tags",excludeTag,!!match);
	var results = [];
	for(var t=0; t<candidates.length; t++) {
		if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
			results.push(candidates[t]);
	}
	if(!sortField)
		sortField = "title";
	results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
	return results;
};

// Returns a list of all tags in use
//   excludeTag - if present, excludes tags that are themselves tagged with excludeTag
// Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
TiddlyWiki.prototype.getTags = function(excludeTag)
{
	var results = [];
	this.forEachTiddler(function(title,tiddler) {
		for(var g=0; g<tiddler.tags.length; g++) {
			var tag = tiddler.tags[g];
			var n = true;
			for(var c=0; c<results.length; c++) {
				if(results[c][0] == tag) {
					n = false;
					results[c][1]++;
				}
			}
			if(n && excludeTag) {
				var t = store.fetchTiddler(tag);
				if(t && t.isTagged(excludeTag))
					n = false;
			}
			if(n)
				results.push([tag,1]);
		}
	});
	results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
	return results;
};

// Return an array of the tiddlers that are tagged with a given tag
TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
{
	return this.reverseLookup("tags",tag,true,sortField);
};

// Return an array of the tiddlers that link to a given tiddler
TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
{
	if(!this.tiddlersUpdated)
		this.updateTiddlers();
	return this.reverseLookup("links",title,true,sortField);
};

// Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
// lookupMatch == true to match tiddlers, false to exclude tiddlers
TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
{
	var results = [];
	this.forEachTiddler(function(title,tiddler) {
		var f = !lookupMatch;
		for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
			if(tiddler[lookupField][lookup] == lookupValue)
				f = lookupMatch;
		}
		if(f)
			results.push(tiddler);
	});
	if(!sortField)
		sortField = "title";
	results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
	return results;
};

// Return the tiddlers as a sorted array
TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
{
	var results = [];
	this.forEachTiddler(function(title,tiddler) {
		if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
			results.push(tiddler);
	});
	if(field)
		results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
	return results;
};

// Return array of names of tiddlers that are referred to but not defined
TiddlyWiki.prototype.getMissingLinks = function(sortField)
{
	if(!this.tiddlersUpdated)
		this.updateTiddlers();
	var results = [];
	this.forEachTiddler(function (title,tiddler) {
		if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
			return;
		for(var n=0; n<tiddler.links.length;n++) {
			var link = tiddler.links[n];
			if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
				results.pushUnique(link);
		}
	});
	results.sort();
	return results;
};

// Return an array of names of tiddlers that are defined but not referred to
TiddlyWiki.prototype.getOrphans = function()
{
	var results = [];
	this.forEachTiddler(function (title,tiddler) {
		if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
			results.push(title);
	});
	results.sort();
	return results;
};

// Return an array of names of all the shadow tiddlers
TiddlyWiki.prototype.getShadowed = function()
{
	var results = [];
	for(var t in config.shadowTiddlers) {
		if(typeof config.shadowTiddlers[t] == "string")
			results.push(t);
	}
	results.sort();
	return results;
};

// Return an array of tiddlers that have been touched since they were downloaded or created
TiddlyWiki.prototype.getTouched = function()
{
	var results = [];
	this.forEachTiddler(function(title,tiddler) {
		if(tiddler.isTouched())
			results.push(tiddler);
		});
	results.sort();
	return results;
};

// Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
TiddlyWiki.prototype.resolveTiddler = function(tiddler)
{
	var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
	return t instanceof Tiddler ? t : null;
};

TiddlyWiki.prototype.getLoader = function()
{
	if(!this.loader)
		this.loader = new TW21Loader();
	return this.loader;
};

TiddlyWiki.prototype.getSaver = function()
{
	if(!this.saver)
		this.saver = new TW21Saver();
	return this.saver;
};

// Filter a list of tiddlers
TiddlyWiki.prototype.filterTiddlers = function(filter)
{
	var results = [];
	if(filter) {
		var tiddler;
		var re = /([^\s\[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
		var match = re.exec(filter);
		while(match) {
			if(match[1] || match[4]) {
				var title = match[1] ? match[1] : match[4];
				tiddler = this.fetchTiddler(title);
				if(tiddler) {
					results.pushUnique(tiddler);
				} else if(store.isShadowTiddler(title)) {
					tiddler = new Tiddler();
					tiddler.set(title,store.getTiddlerText(title));
					results.pushUnique(tiddler);
				}
			} else if(match[2]) {
				switch(match[2]) {
					case "tag":
						var matched = this.getTaggedTiddlers(match[3]);
						for(var m = 0; m < matched.length; m++)
							results.pushUnique(matched[m]);
						break;
					case "sort":
						results = this.sortTiddlers(results,match[3]);
						break;
				}
			}
			match = re.exec(filter);
		}
	}
	return results;
};

// Sort a list of tiddlers
TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
{
	var asc = +1;
	switch(field.substr(0,1)) {
		case "-":
			asc = -1;
			// Note: this fall-through is intentional
		case "+":
			field = field.substr(1);
			break;
	}
	if(TiddlyWiki.standardFieldAccess[field])
		tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
	else
		tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
	return tiddlers;
};
// Returns true if path is a valid field name (path),
// i.e. a sequence of identifiers, separated by '.'
TiddlyWiki.isValidFieldName = function(name)
{
	var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
	return match && (match[0] == name);
};

// Throws an exception when name is not a valid field name.
TiddlyWiki.checkFieldName = function(name)
{
	if(!TiddlyWiki.isValidFieldName(name))
		throw config.messages.invalidFieldName.format([name]);
};

function StringFieldAccess(n,readOnly)
{
	this.set = readOnly ?
			function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
			function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
	this.get = function(t) {return t[n];};
}

function DateFieldAccess(n)
{
	this.set = function(t,v) {
		var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
		if(d != t[n]) {
			t[n] = d; return true;
		}
	};
	this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
}

function LinksFieldAccess(n)
{
	this.set = function(t,v) {
		var s = (typeof v == "string") ? v.readBracketedList() : v;
		if(s.toString() != t[n].toString()) {
			t[n] = s; return true;
		}
	};
	this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
}

TiddlyWiki.standardFieldAccess = {
	// The set functions return true when setting the data has changed the value.
	"title":    new StringFieldAccess("title",true),
	// Handle the "tiddler" field name as the title
	"tiddler":  new StringFieldAccess("title",true),
	"text":     new StringFieldAccess("text"),
	"modifier": new StringFieldAccess("modifier"),
	"modified": new DateFieldAccess("modified"),
	"created":  new DateFieldAccess("created"),
	"tags":     new LinksFieldAccess("tags")
};

TiddlyWiki.isStandardField = function(name)
{
	return TiddlyWiki.standardFieldAccess[name] != undefined;
};

// Sets the value of the given field of the tiddler to the value.
// Setting an ExtendedField's value to null or undefined removes the field.
// Setting a namespace to undefined removes all fields of that namespace.
// The fieldName is case-insensitive.
// All values will be converted to a string value.
TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
{
	TiddlyWiki.checkFieldName(fieldName);
	var t = this.resolveTiddler(tiddler);
	if(!t)
		return;
	fieldName = fieldName.toLowerCase();
	var isRemove = (value === undefined) || (value === null);
	var accessor = TiddlyWiki.standardFieldAccess[fieldName];
	if(accessor) {
		if(isRemove)
			// don't remove StandardFields
			return;
		var h = TiddlyWiki.standardFieldAccess[fieldName];
		if(!h.set(t,value))
			return;
	} else {
		var oldValue = t.fields[fieldName];
		if(isRemove) {
			if(oldValue !== undefined) {
				// deletes a single field
				delete t.fields[fieldName];
			} else {
				// no concrete value is defined for the fieldName
				// so we guess this is a namespace path.
				// delete all fields in a namespace
				var re = new RegExp('^'+fieldName+'\\.');
				var dirty = false;
				for(var n in t.fields) {
					if(n.match(re)) {
						delete t.fields[n];
						dirty = true;
					}
				}
				if(!dirty)
					return;
			}
		} else {
			// the "normal" set case. value is defined (not null/undefined)
			// For convenience provide a nicer conversion Date->String
			value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
			if(oldValue == value)
				return;
			t.fields[fieldName] = value;
		}
	}
	// When we are here the tiddler/store really was changed.
	this.notify(t.title,true);
	if(!fieldName.match(/^temp\./))
		this.setDirty(true);
};

// Returns the value of the given field of the tiddler.
// The fieldName is case-insensitive.
// Will only return String values (or undefined).
TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
{
	var t = this.resolveTiddler(tiddler);
	if(!t)
		return undefined;
	fieldName = fieldName.toLowerCase();
	var accessor = TiddlyWiki.standardFieldAccess[fieldName];
	if(accessor) {
		return accessor.get(t);
	}
	return t.fields[fieldName];
};

// Calls the callback function for every field in the tiddler.
// When callback function returns a non-false value the iteration stops
// and that value is returned.
// The order of the fields is not defined.
// @param callback a function(tiddler,fieldName,value).
TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
{
	var t = this.resolveTiddler(tiddler);
	if(!t)
		return undefined;
	var n,result;
	for(n in t.fields) {
		result = callback(t,n,t.fields[n]);
		if(result)
			return result;
		}
	if(onlyExtendedFields)
		return undefined;
	for(n in TiddlyWiki.standardFieldAccess) {
		if(n == "tiddler")
			// even though the "title" field can also be referenced through the name "tiddler"
			// we only visit this field once.
			continue;
		result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
		if(result)
			return result;
	}
	return undefined;
};

//--
//-- Story functions
//--

function Story(containerId,idPrefix)
{
	this.container = containerId;
	this.idPrefix = idPrefix;
	this.highlightRegExp = null;
	this.tiddlerId = function(title) {
		return this.idPrefix + title;
	};
	this.containerId = function() {
		return this.container;
	};
}

Story.prototype.forEachTiddler = function(fn)
{
	var place = this.getContainer();
	if(!place)
		return;
	var e = place.firstChild;
	while(e) {
		var n = e.nextSibling;
		var title = e.getAttribute("tiddler");
		fn.call(this,title,e);
		e = n;
	}
};

Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
{
	for(var t = titles.length-1;t>=0;t--)
		this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
};

Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle)
{
	var title = (tiddler instanceof Tiddler)? tiddler.title : tiddler;
	var tiddlerElem = this.getTiddler(title);
	if(tiddlerElem) {
		if(toggle)
			this.closeTiddler(title,true);
		else
			this.refreshTiddler(title,template,false,customFields);
	} else {
		var place = this.getContainer();
		var before = this.positionTiddler(srcElement);
		tiddlerElem = this.createTiddler(place,before,title,template,customFields);
	}
	if(srcElement && typeof srcElement !== "string") {
		if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
			anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
		else
			window.scrollTo(0,ensureVisible(tiddlerElem));
	}
};

Story.prototype.positionTiddler = function(srcElement)
{
	var place = this.getContainer();
	var before = null;
	if(typeof srcElement == "string") {
		switch(srcElement) {
			case "top":
				before = place.firstChild;
				break;
			case "bottom":
				before = null;
				break;
		}
	} else {
		var after = this.findContainingTiddler(srcElement);
		if(after == null) {
			before = place.firstChild;
		} else if(after.nextSibling) {
			before = after.nextSibling;
			if(before.nodeType != 1)
				before = null;
		}
	}
	return before;
};

Story.prototype.createTiddler = function(place,before,title,template,customFields)
{
	var tiddlerElem = createTiddlyElement(null,"div",this.tiddlerId(title),"tiddler");
	tiddlerElem.setAttribute("refresh","tiddler");
	if(customFields)
		tiddlerElem.setAttribute("tiddlyFields",customFields);
	place.insertBefore(tiddlerElem,before);
	var defaultText = null;
	if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
		defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
	this.refreshTiddler(title,template,false,customFields,defaultText);
	return tiddlerElem;
};

Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
{
	var tiddler = new Tiddler(title);
	tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields ? fields : {});
	var serverType = tiddler.getServerType();
	var host = tiddler.fields['server.host'];
	var workspace = tiddler.fields['server.workspace'];
	if(!serverType || !host)
		return null;
	var sm = new SyncMachine(serverType,{
			start: function() {
				return this.openHost(host,"openWorkspace");
			},
			openWorkspace: function() {
				return this.openWorkspace(workspace,"getTiddler");
			},
			getTiddler: function() {
				return this.getTiddler(title,"onGetTiddler");
			},
			onGetTiddler: function(context) {
				var tiddler = context.tiddler;
				if(tiddler && tiddler.text) {
					var downloaded = new Date();
					if(!tiddler.created)
						tiddler.created = downloaded;
					if(!tiddler.modified)
						tiddler.modified = tiddler.created;
					store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
					autoSaveChanges();
				}
				delete this;
				return true;
			},
			error: function(message) {
				displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
			}
		});
	sm.go();
	return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
};

Story.prototype.chooseTemplateForTiddler = function(title,template)
{
	if(!template)
		template = DEFAULT_VIEW_TEMPLATE;
	if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
		template = config.tiddlerTemplates[template];
	return template;
};

Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
{
	return store.getRecursiveTiddlerText(template,null,10);
};

Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
{
	var tiddlerElem = this.getTiddler(title);
	if(tiddlerElem) {
		if(tiddlerElem.getAttribute("dirty") == "true" && !force)
			return tiddlerElem;
		template = this.chooseTemplateForTiddler(title,template);
		var currTemplate = tiddlerElem.getAttribute("template");
		if((template != currTemplate) || force) {
			var tiddler = store.getTiddler(title);
			if(!tiddler) {
				tiddler = new Tiddler();
				if(store.isShadowTiddler(title)) {
					tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
				} else {
					var text = template=="EditTemplate" ?
								config.views.editor.defaultText.format([title]) :
								config.views.wikified.defaultText.format([title]);
					text = defaultText ? defaultText : text;
					var fields = customFields ? customFields.decodeHashMap() : null;
					tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
				}
			}
			tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
			tiddlerElem.setAttribute("tiddler",title);
			tiddlerElem.setAttribute("template",template);
			tiddlerElem.onmouseover = this.onTiddlerMouseOver;
			tiddlerElem.onmouseout = this.onTiddlerMouseOut;
			tiddlerElem.ondblclick = this.onTiddlerDblClick;
			tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
			tiddlerElem.innerHTML = this.getTemplateForTiddler(title,template,tiddler);
			applyHtmlMacros(tiddlerElem,tiddler);
			if(store.getTaggedTiddlers(title).length > 0)
				addClass(tiddlerElem,"isTag");
			else
				removeClass(tiddlerElem,"isTag");
			if(store.tiddlerExists(title)) {
				removeClass(tiddlerElem,"shadow");
				removeClass(tiddlerElem,"missing");
			} else {
				addClass(tiddlerElem,store.isShadowTiddler(title) ? "shadow" : "missing");
			}
			if(customFields)
				this.addCustomFields(tiddlerElem,customFields);
			forceReflow();
		}
	}
	return tiddlerElem;
};

Story.prototype.addCustomFields = function(place,customFields)
{
	var fields = customFields.decodeHashMap();
	var w = document.createElement("div");
	w.style.display = "none";
	place.appendChild(w);
	for(var t in fields) {
		var e = document.createElement("input");
		e.setAttribute("type","text");
		e.setAttribute("value",fields[t]);
		w.appendChild(e);
		e.setAttribute("edit",t);
	}
};

Story.prototype.refreshAllTiddlers = function(force)
{
	var place = this.getContainer();
	var e = place.firstChild;
	if(!e)
		return;
	this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
	while((e = e.nextSibling) != null)
		this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
};

Story.prototype.onTiddlerMouseOver = function(e)
{
	if(window.addClass instanceof Function)
		addClass(this,"selected");
};

Story.prototype.onTiddlerMouseOut = function(e)
{
	if(window.removeClass instanceof Function)
		removeClass(this,"selected");
};

Story.prototype.onTiddlerDblClick = function(ev)
{
	var e = ev ? ev : window.event;
	var theTarget = resolveTarget(e);
	if(theTarget && theTarget.nodeName.toLowerCase() != "input" && theTarget.nodeName.toLowerCase() != "textarea") {
		if(document.selection && document.selection.empty)
			document.selection.empty();
		config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
		e.cancelBubble = true;
		if(e.stopPropagation) e.stopPropagation();
		return true;
	} else {
		return false;
	}
};

Story.prototype.onTiddlerKeyPress = function(ev)
{
	var e = ev ? ev : window.event;
	clearMessage();
	var consume = false;
	var title = this.getAttribute("tiddler");
	var target = resolveTarget(e);
	switch(e.keyCode) {
		case 9: // Tab
			if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
				replaceSelection(target,String.fromCharCode(9));
				consume = true;
			}
			if(config.isOpera) {
				target.onblur = function() {
					this.focus();
					this.onblur = null;
				};
			}
			break;
		case 13: // Ctrl-Enter
		case 10: // Ctrl-Enter on IE PC
		case 77: // Ctrl-Enter is "M" on some platforms
			if(e.ctrlKey) {
				blurElement(this);
				config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
				consume = true;
			}
			break;
		case 27: // Escape
			blurElement(this);
			config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
			consume = true;
			break;
	}
	e.cancelBubble = consume;
	if(consume) {
		if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
		e.returnValue = true; // Cancel The Event in IE
		if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
	}
	return !consume;
};

Story.prototype.getTiddlerField = function(title,field)
{
	var tiddlerElem = this.getTiddler(title);
	var e = null;
	if(tiddlerElem != null) {
		var children = tiddlerElem.getElementsByTagName("*");
		for(var t=0; t<children.length; t++) {
			var c = children[t];
			if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
				if(!e)
					e = c;
				if(c.getAttribute("edit") == field)
					e = c;
			}
		}
	}
	return e;
};

Story.prototype.focusTiddler = function(title,field)
{
	var e = this.getTiddlerField(title,field);
	if(e) {
		e.focus();
		e.select();
	}
};

Story.prototype.blurTiddler = function(title)
{
	var tiddlerElem = this.getTiddler(title);
	if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur) {
		tiddlerElem.focus();
		tiddlerElem.blur();
	}
};

Story.prototype.setTiddlerField = function(title,tag,mode,field)
{
	var c = story.getTiddlerField(title,field);

	var tags = c.value.readBracketedList();
	tags.setItem(tag,mode);
	c.value = String.encodeTiddlyLinkList(tags);
};

Story.prototype.setTiddlerTag = function(title,tag,mode)
{
	Story.prototype.setTiddlerField(title,tag,mode,"tags");
};

Story.prototype.closeTiddler = function(title,animate,unused)
{
	var tiddlerElem = this.getTiddler(title);
	if(tiddlerElem != null) {
		clearMessage();
		this.scrubTiddler(tiddlerElem);
		if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
			anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
		else {
			removeNode(tiddlerElem);
			forceReflow();
		}
	}
};

Story.prototype.scrubTiddler = function(tiddlerElem)
{
	tiddlerElem.id = null;
};

Story.prototype.setDirty = function(title,dirty)
{
	var tiddlerElem = this.getTiddler(title);
	if(tiddlerElem != null)
		tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
};

Story.prototype.isDirty = function(title)
{
	var tiddlerElem = this.getTiddler(title);
	if(tiddlerElem != null)
		return tiddlerElem.getAttribute("dirty") == "true";
	return null;
};

Story.prototype.areAnyDirty = function()
{
	var r = false;
	this.forEachTiddler(function(title,element) {
		if(this.isDirty(title))
			r = true;
	});
	return r;
};

Story.prototype.closeAllTiddlers = function(exclude)
{
	clearMessage();
	this.forEachTiddler(function(title,element) {
		if((title != exclude) && element.getAttribute("dirty") != "true")
			this.closeTiddler(title);
	});
	window.scrollTo(0,ensureVisible(this.container));
};

Story.prototype.isEmpty = function()
{
	var place = this.getContainer();
	return place && place.firstChild == null;
};

Story.prototype.search = function(text,useCaseSensitive,useRegExp)
{
	this.closeAllTiddlers();
	highlightHack = new RegExp(useRegExp ?	 text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
	var matches = store.search(highlightHack,"title","excludeSearch");
	this.displayTiddlers(null,matches);
	highlightHack = null;
	var q = useRegExp ? "/" : "'";
	if(matches.length > 0)
		displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
	else
		displayMessage(config.macros.search.failureMsg.format([q + text + q]));
};

Story.prototype.findContainingTiddler = function(e)
{
	while(e && !hasClass(e,"tiddler"))
		e = e.parentNode;
	return e;
};

Story.prototype.gatherSaveFields = function(e,fields)
{
	if(e && e.getAttribute) {
		var f = e.getAttribute("edit");
		if(f)
			fields[f] = e.value.replace(/\r/mg,"");
		if(e.hasChildNodes()) {
			var c = e.childNodes;
			for(var t=0; t<c.length; t++)
				this.gatherSaveFields(c[t],fields);
		}
	}
};

Story.prototype.hasChanges = function(title)
{
	var e = this.getTiddler(title);
	if(e != null) {
		var fields = {};
		this.gatherSaveFields(e,fields);
		var tiddler = store.fetchTiddler(title);
		if(!tiddler)
			return false;
		for(var n in fields) {
			if(store.getValue(title,n) != fields[n])
				return true;
		}
	}
	return false;
};

Story.prototype.saveTiddler = function(title,minorUpdate)
{
	var tiddlerElem = this.getTiddler(title);
	if(tiddlerElem != null) {
		var fields = {};
		this.gatherSaveFields(tiddlerElem,fields);
		var newTitle = fields.title ? fields.title : title;
		if(!store.tiddlerExists(newTitle))
			newTitle = newTitle.trim();
		if(store.tiddlerExists(newTitle) && newTitle != title) {
			if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
				return null;
		}
		if(newTitle != title)
			this.closeTiddler(newTitle,false);
		tiddlerElem.id = this.tiddlerId(newTitle);
		tiddlerElem.setAttribute("tiddler",newTitle);
		tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
		tiddlerElem.setAttribute("dirty","false");
		if(config.options.chkForceMinorUpdate)
			minorUpdate = !minorUpdate;
		if(!store.tiddlerExists(newTitle))
			minorUpdate = false;
		var newDate = new Date();
		var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle!=title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : {});
		for(var n in fields) {
			if(!TiddlyWiki.isStandardField(n))
				extendedFields[n] = fields[n];
		}
		var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
		autoSaveChanges(null,[tiddler]);
		return newTitle;
	}
	return null;
};

Story.prototype.permaView = function()
{
	var links = [];
	this.forEachTiddler(function(title,element) {
		links.push(String.encodeTiddlyLink(title));
	});
	var t = encodeURIComponent(links.join(" "));
	if(t == "")
		t = "#";
	if(window.location.hash != t)
		window.location.hash = t;
};

Story.prototype.switchTheme = function(theme)
{
	if(safeMode)
		return;

	isAvailable = function(title) {
		var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
		if(s!=-1)
			title = title.substr(0,s);
		return store.tiddlerExists(title) || store.isShadowTiddler(title);
	};

	getSlice = function(theme,slice) {
		var r;
		if(readOnly)
			r = store.getTiddlerSlice(theme,slice+"ReadOnly") || store.getTiddlerSlice(theme,"Web"+slice);
		r = r || store.getTiddlerSlice(theme,slice);
		if(r && r.indexOf(config.textPrimitives.sectionSeparator)==0)
			r = theme + r;
		return isAvailable(r) ? r : slice;
	};

	replaceNotification = function(i,name,theme,slice) {
		var newName = getSlice(theme,slice);
		if(name!=newName && store.namedNotifications[i].name==name) {
			store.namedNotifications[i].name = newName;
			return newName;
		}
		return name;
	};

	var pt = config.refresherData.pageTemplate;
	var vi = DEFAULT_VIEW_TEMPLATE;
	var vt = config.tiddlerTemplates[vi];
	var ei = DEFAULT_EDIT_TEMPLATE;
	var et = config.tiddlerTemplates[ei];

	for(var i=0; i<config.notifyTiddlers.length; i++) {
		var name = config.notifyTiddlers[i].name;
		switch(name) {
		case "PageTemplate":
			config.refresherData.pageTemplate = replaceNotification(i,config.refresherData.pageTemplate,theme,name);
			break;
		case "StyleSheet":
			removeStyleSheet(config.refresherData.styleSheet);
			config.refresherData.styleSheet = replaceNotification(i,config.refresherData.styleSheet,theme,name);
			break;
		case "ColorPalette":
			config.refresherData.colorPalette = replaceNotification(i,config.refresherData.colorPalette,theme,name);
			break;
		default:
			break;
		}
	}
	config.tiddlerTemplates[vi] = getSlice(theme,"ViewTemplate");
	config.tiddlerTemplates[ei] = getSlice(theme,"EditTemplate");
	if(!startingUp) {
		if(config.refresherData.pageTemplate!=pt || config.tiddlerTemplates[vi]!=vt || config.tiddlerTemplates[ei]!=et) {
			refreshAll();
			story.refreshAllTiddlers(true);
		} else {
			setStylesheet(store.getRecursiveTiddlerText(config.refresherData.styleSheet,"",10),config.refreshers.styleSheet);
		}
		config.options.txtTheme = theme;
		saveOptionCookie("txtTheme");
	}
};

Story.prototype.getTiddler = function(title)
{
	return document.getElementById(this.tiddlerId(title));
};

Story.prototype.getContainer = function()
{
	return document.getElementById(this.containerId());
};

//--
//-- Backstage
//--

var backstage = {
	area: null,
	toolbar: null,
	button: null,
	showButton: null,
	hideButton: null,
	cloak: null,
	panel: null,
	panelBody: null,
	panelFooter: null,
	currTabName: null,
	currTabElem: null,
	content: null,

	init: function() {
		var cmb = config.messages.backstage;
		this.area = document.getElementById("backstageArea");
		this.toolbar = document.getElementById("backstageToolbar");
		this.button = document.getElementById("backstageButton");
		this.button.style.display = "block";
		var t = cmb.open.text + " " + glyph("bentArrowLeft");
		this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
						function (e) {backstage.show(); return false;},null,"backstageShow");
		t = glyph("bentArrowRight") + " " + cmb.close.text;
		this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
						function (e) {backstage.hide(); return false;},null,"backstageHide");
		this.cloak = document.getElementById("backstageCloak");
		this.panel = document.getElementById("backstagePanel");
		this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
		this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
		this.cloak.onmousedown = function(e) {
			backstage.switchTab(null);
		};
		createTiddlyText(this.toolbar,cmb.prompt);
		for(t=0; t<config.backstageTasks.length; t++) {
			var taskName = config.backstageTasks[t];
			var task = config.tasks[taskName];
			var handler = task.action ? this.onClickCommand : this.onClickTab;
			var text = task.text + (task.action ? "" : glyph("downTriangle"));
			var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
			btn.setAttribute("task",taskName);
			addClass(btn,task.action ? "backstageAction" : "backstageTask");
			}
		this.content = document.getElementById("contentWrapper");
		if(config.options.chkBackstage)
			this.show();
		else
			this.hide();
	},

	isVisible: function() {
		return this.area ? this.area.style.display == "block" : false;
	},

	show: function() {
		this.area.style.display = "block";
		if(anim && config.options.chkAnimate) {
			backstage.toolbar.style.left = findWindowWidth() + "px";
			var p = [
				{style: "left", start: findWindowWidth(), end: 0, template: "%0px"}
			];
			anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
		} else {
			backstage.area.style.left = "0px";
		}
		this.showButton.style.display = "none";
		this.hideButton.style.display = "block";
		config.options.chkBackstage = true;
		saveOptionCookie("chkBackstage");
		addClass(this.content,"backstageVisible");
	},

	hide: function() {
		if(this.currTabElem) {
			this.switchTab(null);
		} else {
			backstage.toolbar.style.left = "0px";
			if(anim && config.options.chkAnimate) {
				var p = [
					{style: "left", start: 0, end: findWindowWidth(), template: "%0px"}
				];
				var c = function(element,properties) {backstage.area.style.display = "none";};
				anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
			} else {
				this.area.style.display = "none";
			}
			this.showButton.style.display = "block";
			this.hideButton.style.display = "none";
			config.options.chkBackstage = false;
			saveOptionCookie("chkBackstage");
			removeClass(this.content,"backstageVisible");
		}
	},

	onClickCommand: function(e) {
		var task = config.tasks[this.getAttribute("task")];
		displayMessage(task);
		if(task.action) {
			backstage.switchTab(null);
			task.action();
		}
		return false;
	},

	onClickTab: function(e) {
		backstage.switchTab(this.getAttribute("task"));
		return false;
	},

	// Switch to a given tab, or none if null is passed
	switchTab: function(tabName) {
		var tabElem = null;
		var e = this.toolbar.firstChild;
		while(e)
			{
			if(e.getAttribute && e.getAttribute("task") == tabName)
				tabElem = e;
			e = e.nextSibling;
			}
		if(tabName == backstage.currTabName)
			return;
		if(backstage.currTabElem) {
			removeClass(this.currTabElem,"backstageSelTab");
		}
		if(tabElem && tabName) {
			backstage.preparePanel();
			addClass(tabElem,"backstageSelTab");
			var task = config.tasks[tabName];
			wikify(task.content,backstage.panelBody,null,null);
			backstage.showPanel();
		} else if(backstage.currTabElem) {
			backstage.hidePanel();
		}
		backstage.currTabName = tabName;
		backstage.currTabElem = tabElem;
	},

	isPanelVisible: function() {
		return backstage.panel ? backstage.panel.style.display == "block" : false;
	},

	preparePanel: function() {
		backstage.cloak.style.height = findWindowHeight() + "px";
		backstage.cloak.style.display = "block";
		removeChildren(backstage.panelBody);
		return backstage.panelBody;
	},

	showPanel: function() {
		backstage.panel.style.display = "block";
		if(anim && config.options.chkAnimate) {
			backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
			var p = [
				{style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}
			];
			anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
		} else {
			backstage.panel.style.top = "0px";
		}
		return backstage.panelBody;
	},

	hidePanel: function() {
		backstage.currTabName = null;
		backstage.currTabElem = null;
		if(anim && config.options.chkAnimate) {
			var p = [
				{style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
				{style: "display", atEnd: "none"}
			];
			var c = function(element,properties) {backstage.cloak.style.display = "none";};
			anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
		 } else {
			backstage.panel.style.display = "none";
			backstage.cloak.style.display = "none";
		}
	}
};

config.macros.backstage = {};

config.macros.backstage.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	var backstageTask = config.tasks[params[0]];
	if(backstageTask)
		createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
};

//--
//-- ImportTiddlers macro
//--

config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	if(readOnly) {
		createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
		return;
	}
	var w = new Wizard();
	w.createWizard(place,this.wizardTitle);
	this.restart(w);
};

config.macros.importTiddlers.onCancel = function(e)
{
	var wizard = new Wizard(this);
	var place = wizard.clear();
	config.macros.importTiddlers.restart(wizard);
	return false;
};

config.macros.importTiddlers.onClose = function(e)
{
	backstage.hidePanel();
	return false;
};

config.macros.importTiddlers.restart = function(wizard)
{
	wizard.addStep(this.step1Title,this.step1Html);
	var s = wizard.getElement("selTypes");
	for(var t in config.adaptors) {
		var e = createTiddlyElement(s,"option",null,null,config.adaptors[t].serverLabel ? config.adaptors[t].serverLabel : t);
		e.value = t;
	}
	if(config.defaultAdaptor)
		s.value = config.defaultAdaptor;
	s = wizard.getElement("selFeeds");
	var feeds = this.getFeeds();
	for(t in feeds) {
		e = createTiddlyElement(s,"option",null,null,t);
		e.value = t;
	}
	wizard.setValue("feeds",feeds);
	s.onchange = config.macros.importTiddlers.onFeedChange;
	var fileInput = wizard.getElement("txtBrowse");
	fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
	fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
	wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
};

config.macros.importTiddlers.getFeeds = function()
{
	var feeds = {};
	var tagged = store.getTaggedTiddlers("systemServer","title");
	for(var t=0; t<tagged.length; t++) {
		var title = tagged[t].title;
		var serverType = store.getTiddlerSlice(title,"Type");
		if(!serverType)
			serverType = "file";
		feeds[title] = {title: title,
						url: store.getTiddlerSlice(title,"URL"),
						workspace: store.getTiddlerSlice(title,"Workspace"),
						workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
						tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
						serverType: serverType,
						description: store.getTiddlerSlice(title,"Description")};
	}
	return feeds;
};

config.macros.importTiddlers.onFeedChange = function(e)
{
	var wizard = new Wizard(this);
	var selTypes = wizard.getElement("selTypes");
	var fileInput = wizard.getElement("txtPath");
	var feeds = wizard.getValue("feeds");
	var f = feeds[this.value];
	if(f) {
		selTypes.value = f.serverType;
		fileInput.value = f.url;
		wizard.setValue("feedName",f.serverType);
		wizard.setValue("feedHost",f.url);
		wizard.setValue("feedWorkspace",f.workspace);
		wizard.setValue("feedWorkspaceList",f.workspaceList);
		wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
	}
	return false;
};

config.macros.importTiddlers.onBrowseChange = function(e)
{
	var wizard = new Wizard(this);
	var fileInput = wizard.getElement("txtPath");
	fileInput.value = "file://" + this.value;
	var serverType = wizard.getElement("selTypes");
	serverType.value = "file";
	return false;
};

config.macros.importTiddlers.onOpen = function(e)
{
	var wizard = new Wizard(this);
	var fileInput = wizard.getElement("txtPath");
	var url = fileInput.value;
	var serverType = wizard.getElement("selTypes").value || config.defaultAdaptor;
	var adaptor = new config.adaptors[serverType];
	wizard.setValue("adaptor",adaptor);
	wizard.setValue("serverType",serverType);
	wizard.setValue("host",url);
	var ret = adaptor.openHost(url,null,wizard,config.macros.importTiddlers.onOpenHost);
	if(ret !== true)
		displayMessage(ret);
	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
	return false;
};

config.macros.importTiddlers.onOpenHost = function(context,wizard)
{
	var adaptor = wizard.getValue("adaptor");
	if(context.status !== true)
		displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
	var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
	if(ret !== true)
		displayMessage(ret);
	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
};

config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
{
	if(context.status !== true)
		displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
	wizard.setValue("context",context);
	var workspace = wizard.getValue("feedWorkspace");
	if(!workspace && context.workspaces.length==1)
		workspace = context.workspaces[0].title;
	if(workspace) {
		var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
		if(ret !== true)
			displayMessage(ret);
		wizard.setValue("workspace",workspace);
		wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
		return;
	}
	wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
	var s = wizard.getElement("selWorkspace");
	s.onchange = config.macros.importTiddlers.onWorkspaceChange;
	for(var t=0; t<context.workspaces.length; t++) {
		var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
		e.value = context.workspaces[t].title;
	}
	var workspaceList = wizard.getValue("feedWorkspaceList");
	if(workspaceList) {
		var list = workspaceList.parseParams("workspace",null,false,true);
		for(var n=1; n<list.length; n++) {
			if(context.workspaces.findByField("title",list[n].value) == null) {
				e = createTiddlyElement(s,"option",null,null,list[n].value);
				e.value = list[n].value;
			}
		}
	}
	if(workspace) {
		t = wizard.getElement("txtWorkspace");
		t.value = workspace;
	}
	wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
};

config.macros.importTiddlers.onWorkspaceChange = function(e)
{
	var wizard = new Wizard(this);
	var t = wizard.getElement("txtWorkspace");
	t.value  = this.value;
	this.selectedIndex = 0;
	return false;
};

config.macros.importTiddlers.onChooseWorkspace = function(e)
{
	var wizard = new Wizard(this);
	var adaptor = wizard.getValue("adaptor");
	var workspace = wizard.getElement("txtWorkspace").value;
	wizard.setValue("workspace",workspace);
	var context = wizard.getValue("context");
	var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
	if(ret !== true)
		displayMessage(ret);
	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
	return false;
};

config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
{
	if(context.status !== true)
		displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
	var adaptor = wizard.getValue("adaptor");
	var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
	if(ret !== true)
		displayMessage(ret);
	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
};

config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
{
	if(context.status !== true) {
		wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.errorGettingTiddlerList);
		return;
	}
	// Extract data for the listview
	var listedTiddlers = [];
	if(context.tiddlers) {
		for(var n=0; n<context.tiddlers.length; n++) {
			var tiddler = context.tiddlers[n];
			listedTiddlers.push({
				title: tiddler.title,
				modified: tiddler.modified,
				modifier: tiddler.modifier,
				text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
				tags: tiddler.tags,
				size: tiddler.text ? tiddler.text.length : 0,
				tiddler: tiddler
			});
		}
	}
	listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
	// Display the listview
	wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
	var markList = wizard.getElement("markList");
	var listWrapper = document.createElement("div");
	markList.parentNode.insertBefore(listWrapper,markList);
	var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
	wizard.setValue("listView",listView);
	var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
	txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
	wizard.setButtons([
			{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
			{caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick:  config.macros.importTiddlers.doImport}
		]);
};

config.macros.importTiddlers.generateSystemServerName = function(wizard)
{
	var serverType = wizard.getValue("serverType");
	var host = wizard.getValue("host");
	var workspace = wizard.getValue("workspace");
	var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
	return pattern.format([serverType,host,workspace]);
};

config.macros.importTiddlers.saveServerTiddler = function(wizard)
{
	var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
	if(store.tiddlerExists(txtSaveTiddler)) {
		if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
			return;
		store.suspendNotifications();
		store.removeTiddler(txtSaveTiddler);
		store.resumeNotifications();
	}
	var serverType = wizard.getValue("serverType");
	var host = wizard.getValue("host");
	var workspace = wizard.getValue("workspace");
	var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
	store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
};

config.macros.importTiddlers.doImport = function(e)
{
	var wizard = new Wizard(this);
	if(wizard.getElement("chkSave").checked)
		config.macros.importTiddlers.saveServerTiddler(wizard);
	var chkSync = wizard.getElement("chkSync").checked;
	wizard.setValue("sync",chkSync);
	var listView = wizard.getValue("listView");
	var rowNames = ListView.getSelectedRows(listView);
	var adaptor = wizard.getValue("adaptor");
	var overwrite = new Array();
	var t;
	for(t=0; t<rowNames.length; t++) {
		if(store.tiddlerExists(rowNames[t]))
			overwrite.push(rowNames[t]);
	}
	if(overwrite.length > 0) {
		if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
			return false;
	}
	wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
	for(t=0; t<rowNames.length; t++) {
		var link = document.createElement("div");
		createTiddlyLink(link,rowNames[t],true);
		var place = wizard.getElement("markReport");
		place.parentNode.insertBefore(link,place);
	}
	wizard.setValue("remainingImports",rowNames.length);
	wizard.setButtons([
			{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
		],config.macros.importTiddlers.statusDoingImport);
	for(t=0; t<rowNames.length; t++) {
		var context = {};
		context.allowSynchronous = true;
		var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
	}
	return false;
};

config.macros.importTiddlers.onGetTiddler = function(context,wizard)
{
	if(!context.status)
		displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
	var tiddler = context.tiddler;
	store.suspendNotifications();
	store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
	if(!wizard.getValue("sync")) {
		store.setValue(tiddler.title,'server',null);
	}
	store.resumeNotifications();
	if(!context.isSynchronous)
		store.notify(tiddler.title,true);
	var remainingImports = wizard.getValue("remainingImports")-1;
	wizard.setValue("remainingImports",remainingImports);
	if(remainingImports == 0) {
		if(context.isSynchronous) {
			store.notifyAll();
			refreshDisplay();
		}
		wizard.setButtons([
				{caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onClose}
			],config.macros.importTiddlers.statusDoneImport);
		autoSaveChanges();
	}
};

//--
//-- Upgrade macro
//--

config.macros.upgrade.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	var w = new Wizard();
	w.createWizard(place,this.wizardTitle);
	w.addStep(this.step1Title,this.step1Html.format([this.source,this.source]));
	w.setButtons([{caption: this.upgradeLabel, tooltip: this.upgradePrompt, onClick: this.onClickUpgrade}]);
};

config.macros.upgrade.onClickUpgrade = function(e)
{
	var me = config.macros.upgrade;
	var w = new Wizard(this);
	if(window.location.protocol != "file:") {
		alert(me.errorCantUpgrade);
		return false;
	}	
	if(story.areAnyDirty() || store.isDirty()) {
		alert(me.errorNotSaved);
		return false;
	}
	var localPath = getLocalPath(document.location.toString());
	var backupPath = getBackupPath(localPath,me.backupExtension);
	w.setValue("backupPath",backupPath);
	w.setButtons([],me.statusPreparingBackup);
	var original = loadOriginal(localPath);
	w.setButtons([],me.statusSavingBackup);
	var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
	if(backup != true) {
		w.setButtons([],me.errorSavingBackup);
		alert(me.errorSavingBackup);
		return false;
	}
	w.setButtons([],me.statusLoadingCore);
	var load = loadRemoteFile(me.source,me.onLoadCore,w);
	if(typeof load == "string") {
		w.setButtons([],me.errorLoadingCore);
		alert(me.errorLoadingCore);
		return false;
	}
	return false;
};

config.macros.upgrade.onLoadCore = function(status,params,responseText,url,xhr)
{
	var me = config.macros.upgrade;
	var w = params;
	var errMsg;
	if(!status)
		errMsg = me.errorLoadingCore;
	var newVer = me.extractVersion(responseText);
	if(!newVer)
		errMsg = me.errorCoreFormat;
	if(errMsg) {
		w.setButtons([],errMsg);
		alert(errMsg);
		return;
	}
	var onStartUpgrade = function(e) {
		w.setButtons([],me.statusSavingCore);
		var localPath = getLocalPath(document.location.toString());
		saveFile(localPath,responseText);
		w.setButtons([],me.statusReloadingCore);
		var backupPath = w.getValue("backupPath");
		var newLoc = document.location.toString() + '?time=' + new Date().convertToYYYYMMDDHHMM()  + '#upgrade:[[' + encodeURI(backupPath) + ']]';
		window.setTimeout(function () {window.location = newLoc;},10)
	};
	var step2 = [me.step2Html_downgrade,me.step2Html_restore,me.step2Html_upgrade][compareVersions(version,newVer) + 1];
	w.addStep(me.step2Title,step2.format([formatVersion(newVer),formatVersion(version)]));
	w.setButtons([{caption: me.startLabel, tooltip: me.startPrompt, onClick: onStartUpgrade},{caption: me.cancelLabel, tooltip: me.cancelPrompt, onClick: me.onCancel}]);
};

config.macros.upgrade.onCancel = function(e)
{
	var me = config.macros.upgrade;
	var w = new Wizard(this);
	w.addStep(me.step3Title,me.step3Html);
	w.setButtons([]);
	return false;
}

config.macros.upgrade.extractVersion = function(upgradeFile)
{
	var re = /^var version = \{title: "([^"]+)", major: (\d+), minor: (\d+), revision: (\d+)(, beta: (\d+)){0,1}, date: new Date\("([^"]+)"\)/mg;
	var m = re.exec(upgradeFile);
	return  m ? {title: m[1], major: m[2], minor: m[3], revision: m[4], beta: m[6], date: new Date(m[7])} : null;
};

function upgradeFrom(path)
{
	var importStore = new TiddlyWiki();
	var tw = loadFile(path);
	if(window.netscape !== undefined)
		tw = convertUTF8ToUnicode(tw);
	importStore.importTiddlyWiki(tw);
	importStore.forEachTiddler(function(title,tiddler) {
		if(!store.getTiddler(title)) {
			store.addTiddler(tiddler);
		}
	});
	refreshDisplay();
	saveChanges(); //# To create appropriate Markup* sections
	alert(config.messages.upgradeDone.format([formatVersion()]));
	window.location = window.location.toString().substr(0,window.location.toString().lastIndexOf('?'));
}

//--
//-- Sync macro
//--

// Synchronisation handlers
config.syncers = {};

// Sync state.
var currSync = null;

// sync macro
config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	if(!wikifier.isStatic)
		this.startSync(place);
};

config.macros.sync.startSync = function(place)
{
	if(currSync)
		config.macros.sync.cancelSync();
	currSync = {};
	currSync.syncList = this.getSyncableTiddlers();
	this.createSyncTasks();
	this.preProcessSyncableTiddlers();
	var wizard = new Wizard();
	currSync.wizard = wizard;
	wizard.createWizard(place,this.wizardTitle);
	wizard.addStep(this.step1Title,this.step1Html);
	var markList = wizard.getElement("markList");
	var listWrapper = document.createElement("div");
	markList.parentNode.insertBefore(listWrapper,markList);
	currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
	this.processSyncableTiddlers();
	wizard.setButtons([
			{caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}
		]);
};

config.macros.sync.getSyncableTiddlers = function()
{
	var list = [];
	store.forEachTiddler(function(title,tiddler) {
		var syncItem = {};
		syncItem.serverType = tiddler.getServerType();
		syncItem.serverHost = tiddler.fields['server.host'];
		syncItem.serverWorkspace = tiddler.fields['server.workspace'];
		syncItem.tiddler = tiddler;
		syncItem.title = tiddler.title;
		syncItem.isTouched = tiddler.isTouched();
		syncItem.selected = syncItem.isTouched;
		syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
		syncItem.status = syncItem.syncStatus.text;
		if(syncItem.serverType && syncItem.serverHost)
			list.push(syncItem);
		});
	list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
	return list;
};

config.macros.sync.preProcessSyncableTiddlers = function()
{
	for(var t=0; t<currSync.syncList.length; t++) {
		si = currSync.syncList[t];
		var ti = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler);
		si.serverUrl = ti.uri;
	}
};

config.macros.sync.processSyncableTiddlers = function()
{
	for(var t=0; t<currSync.syncList.length; t++) {
		si = currSync.syncList[t];
		si.rowElement.style.backgroundColor = si.syncStatus.color;
	}
};

config.macros.sync.createSyncTasks = function()
{
	currSync.syncTasks = [];
	for(var t=0; t<currSync.syncList.length; t++) {
		var si = currSync.syncList[t];
		var r = null;
		for(var st=0; st<currSync.syncTasks.length; st++) {
			var cst = currSync.syncTasks[st];
			if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
				r = cst;
		}
		if(r == null) {
			si.syncTask = this.createSyncTask(si);
			currSync.syncTasks.push(si.syncTask);
		} else {
			si.syncTask = r;
			r.syncItems.push(si);
		}
	}
};

config.macros.sync.createSyncTask = function(syncItem)
{
	var st = {};
	st.serverType = syncItem.serverType;
	st.serverHost = syncItem.serverHost;
	st.serverWorkspace = syncItem.serverWorkspace;
	st.syncItems = [syncItem];
	st.syncMachine = new SyncMachine(st.serverType,{
		start: function() {
			return this.openHost(st.serverHost,"openWorkspace");
		},
		openWorkspace: function() {
			return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
		},
		getTiddlerList: function() {
			return this.getTiddlerList("onGetTiddlerList");
		},
		onGetTiddlerList: function(context) {
			var tiddlers = context.tiddlers;
			for(var t=0; t<st.syncItems.length; t++) {
				var si = st.syncItems[t];
				var f = tiddlers.findByField("title",si.title);
				if(f !== null) {
					if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
						si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
					}
				} else {
					si.syncStatus = config.macros.sync.syncStatusList.notFound;
				}
				config.macros.sync.updateSyncStatus(si);
			}
		},
		getTiddler: function(title) {
			return this.getTiddler(title,"onGetTiddler");
		},
		onGetTiddler: function(context) {
			var tiddler = context.tiddler;
			var syncItem = st.syncItems.findByField("title",tiddler.title);
			if(syncItem !== null) {
				syncItem = st.syncItems[syncItem];
				store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
				syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
				config.macros.sync.updateSyncStatus(syncItem);
			}
		},
		putTiddler: function(tiddler) {
			return this.putTiddler(tiddler,"onPutTiddler");
		},
		onPutTiddler: function(context) {
			var title = context.title;
			var syncItem = st.syncItems.findByField("title",title);
			if(syncItem !== null) {
				syncItem = st.syncItems[syncItem];
				store.resetTiddler(title);
				syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
				config.macros.sync.updateSyncStatus(syncItem);
			}
		}
	});
	st.syncMachine.go();
	return st;
};

config.macros.sync.updateSyncStatus = function(syncItem)
{
	var e = syncItem.colElements["status"];
	removeChildren(e);
	createTiddlyText(e,syncItem.syncStatus.text);
	syncItem.rowElement.style.backgroundColor = syncItem.syncStatus.color;
};

config.macros.sync.doSync = function(e)
{
	var rowNames = ListView.getSelectedRows(currSync.listView);
	for(var t=0; t<currSync.syncList.length; t++) {
		var si = currSync.syncList[t];
		if(rowNames.indexOf(si.title) != -1) {
			config.macros.sync.doSyncItem(si);
		}
	}
	return false;
};

config.macros.sync.doSyncItem = function(syncItem)
{
	var r = true;
	var sl = config.macros.sync.syncStatusList;
	switch(syncItem.syncStatus) {
		case sl.changedServer:
			r = syncItem.syncTask.syncMachine.go("getTiddler",syncItem.title);
			break;
		case sl.notFound:
		case sl.changedLocally:
		case sl.changedBoth:
			r = syncItem.syncTask.syncMachine.go("putTiddler",syncItem.tiddler);
			break;
		default:
			break;
	}
	if(r !== true)
		displayMessage("Error in doSyncItem: " + r);
};

config.macros.sync.cancelSync = function()
{
	currSync = null;
};

function SyncMachine(serverType,steps)
{
	this.serverType = serverType;
	this.adaptor = new config.adaptors[serverType];
	this.steps = steps;
}

SyncMachine.prototype.go = function(step,context)
{
	var r = context ? context.status : null;
	if(typeof r == "string") {
		this.invokeError(r);
		return r;
	}
	var h = this.steps[step ? step : "start"];
	if(!h)
		return null;
	r = h.call(this,context);
	if(typeof r == "string")
		this.invokeError(r);
	return r;
};

SyncMachine.prototype.invokeError = function(message)
{
	if(this.steps.error)
		this.steps.error(message);
};

SyncMachine.prototype.openHost = function(host,nextStep)
{
	var me = this;
	return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
};

SyncMachine.prototype.getWorkspaceList = function(nextStep)
{
	var me = this;
	return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
};

SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
{
	var me = this;
	return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
};

SyncMachine.prototype.getTiddlerList = function(nextStep)
{
	var me = this;
	return me.adaptor.getTiddlerList(null,null,function(context) {me.go(nextStep,context);});
};

SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
{
	return this.adaptor.generateTiddlerInfo(tiddler);
};

SyncMachine.prototype.getTiddler = function(title,nextStep)
{
	var me = this;
	return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
};

SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
{
	var me = this;
	return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
};

//--
//-- Manager UI for groups of tiddlers
//--

config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	var wizard = new Wizard();
	wizard.createWizard(place,this.wizardTitle);
	wizard.addStep(this.step1Title,this.step1Html);
	var markList = wizard.getElement("markList");
	var listWrapper = document.createElement("div");
	markList.parentNode.insertBefore(listWrapper,markList);
	listWrapper.setAttribute("refresh","macro");
	listWrapper.setAttribute("macroName","plugins");
	listWrapper.setAttribute("params",paramString);
	this.refresh(listWrapper,paramString);
};

config.macros.plugins.refresh = function(listWrapper,params)
{
	var wizard = new Wizard(listWrapper);
	var selectedRows = [];
	ListView.forEachSelector(listWrapper,function(e,rowName) {
			if(e.checked)
				selectedRows.push(e.getAttribute("rowName"));
		});
	removeChildren(listWrapper);
	params = params.parseParams("anon");
	var plugins = installedPlugins.slice(0);
	var t,tiddler,p;
	var configTiddlers = store.getTaggedTiddlers("systemConfig");
	for(t=0; t<configTiddlers.length; t++) {
		tiddler = configTiddlers[t];
		if(plugins.findByField("title",tiddler.title) == null) {
			p = getPluginInfo(tiddler);
			p.executed = false;
			p.log.splice(0,0,this.skippedText);
			plugins.push(p);
		}
	}
	for(t=0; t<plugins.length; t++) {
		p = plugins[t];
		p.size = p.tiddler.text ? p.tiddler.text.length : 0;
		p.forced = p.tiddler.isTagged("systemConfigForce");
		p.disabled = p.tiddler.isTagged("systemConfigDisable");
		p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
	}
	if(plugins.length == 0) {
		createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
		wizard.setButtons([]);
	} else {
		var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
		wizard.setValue("listView",listView);
		wizard.setButtons([
				{caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick:  config.macros.plugins.doRemoveTag},
				{caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick:  config.macros.plugins.doDelete}
			]);
	}
};

config.macros.plugins.doRemoveTag = function(e)
{
	var wizard = new Wizard(this);
	var listView = wizard.getValue("listView");
	var rowNames = ListView.getSelectedRows(listView);
	if(rowNames.length == 0) {
		alert(config.messages.nothingSelected);
	} else {
		for(var t=0; t<rowNames.length; t++)
			store.setTiddlerTag(rowNames[t],false,"systemConfig");
	}
};

config.macros.plugins.doDelete = function(e)
{
	var wizard = new Wizard(this);
	var listView = wizard.getValue("listView");
	var rowNames = ListView.getSelectedRows(listView);
	if(rowNames.length == 0) {
		alert(config.messages.nothingSelected);
	} else {
		if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
			for(t=0; t<rowNames.length; t++) {
				store.removeTiddler(rowNames[t]);
				story.closeTiddler(rowNames[t],true);
			}
		}
	}
};

//--
//-- Message area
//--

function getMessageDiv()
{
	var msgArea = document.getElementById("messageArea");
	if(!msgArea)
		return null;
	if(!msgArea.hasChildNodes())
		createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
			config.messages.messageClose.text,
			config.messages.messageClose.tooltip,
			clearMessage);
	msgArea.style.display = "block";
	return createTiddlyElement(msgArea,"div");
}

function displayMessage(text,linkText)
{
	var e = getMessageDiv();
	if(!e) {
		alert(text);
		return;
	}
	if(linkText) {
		var link = createTiddlyElement(e,"a",null,null,text);
		link.href = linkText;
		link.target = "_blank";
	} else {
		e.appendChild(document.createTextNode(text));
	}
}

function clearMessage()
{
	var msgArea = document.getElementById("messageArea");
	if(msgArea) {
		removeChildren(msgArea);
		msgArea.style.display = "none";
	}
	return false;
}

//--
//-- Refresh mechanism
//--

config.refreshers = {
	link: function(e,changeList)
		{
		var title = e.getAttribute("tiddlyLink");
		refreshTiddlyLink(e,title);
		return true;
		},

	tiddler: function(e,changeList)
		{
		var title = e.getAttribute("tiddler");
		var template = e.getAttribute("template");
		if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
			story.refreshTiddler(title,template,true);
		else
			refreshElements(e,changeList);
		return true;
		},

	content: function(e,changeList)
		{
		var title = e.getAttribute("tiddler");
		var force = e.getAttribute("force");
		if(force != null || changeList == null || changeList.indexOf(title) != -1) {
			removeChildren(e);
			wikify(store.getTiddlerText(title,title),e,null,store.fetchTiddler(title));
			return true;
		} else
			return false;
		},

	macro: function(e,changeList)
		{
		var macro = e.getAttribute("macroName");
		var params = e.getAttribute("params");
		if(macro)
			macro = config.macros[macro];
		if(macro && macro.refresh)
			macro.refresh(e,params);
		return true;
		}
};

config.refresherData = {
	styleSheet: "StyleSheet",
	defaultStyleSheet: "StyleSheet",
	pageTemplate: "PageTemplate",
	defaultPageTemplate: "PageTemplate",
	colorPalette: "ColorPalette",
	defaultColorPalette: "ColorPalette"
};

function refreshElements(root,changeList)
{
	var nodes = root.childNodes;
	for(var c=0; c<nodes.length; c++) {
		var e = nodes[c], type = null;
		if(e.getAttribute  && (e.tagName ? e.tagName != "IFRAME" : true))
			type = e.getAttribute("refresh");
		var refresher = config.refreshers[type];
		var refreshed = false;
		if(refresher != undefined)
			refreshed = refresher(e,changeList);
		if(e.hasChildNodes() && !refreshed)
			refreshElements(e,changeList);
	}
}

function applyHtmlMacros(root,tiddler)
{
	var e = root.firstChild;
	while(e) {
		var nextChild = e.nextSibling;
		if(e.getAttribute) {
			var macro = e.getAttribute("macro");
			if(macro) {
				var params = "";
				var p = macro.indexOf(" ");
				if(p != -1) {
					params = macro.substr(p+1);
					macro = macro.substr(0,p);
				}
				invokeMacro(e,macro,params,null,tiddler);
			}
		}
		if(e.hasChildNodes())
			applyHtmlMacros(e,tiddler);
		e = nextChild;
	}
}

function refreshPageTemplate(title)
{
	var stash = createTiddlyElement(document.body,"div");
	stash.style.display = "none";
	var display = story.getContainer();
	var nodes,t;
	if(display) {
		nodes = display.childNodes;
		for(t=nodes.length-1; t>=0; t--)
			stash.appendChild(nodes[t]);
	}
	var wrapper = document.getElementById("contentWrapper");

	isAvailable = function(title) {
		var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
		if(s!=-1)
			title = title.substr(0,s);
		return store.tiddlerExists(title) || store.isShadowTiddler(title);
	};
	if(!title || !isAvailable(title))
		title = config.refresherData.pageTemplate;
	if(!isAvailable(title))
		title = config.refresherData.defaultPageTemplate; //# this one is always avaialable
	html = store.getRecursiveTiddlerText(title,null,10);
	wrapper.innerHTML = html;
	applyHtmlMacros(wrapper);
	refreshElements(wrapper);
	display = story.getContainer();
	removeChildren(display);
	if(!display)
		display = createTiddlyElement(wrapper,"div",story.containerId());
	nodes = stash.childNodes;
	for(t=nodes.length-1; t>=0; t--)
		display.appendChild(nodes[t]);
	removeNode(stash);
}

function refreshDisplay(hint)
{
	if(typeof hint == "string")
		hint = [hint];
	var e = document.getElementById("contentWrapper");
	refreshElements(e,hint);
	if(backstage.isPanelVisible()) {
		e = document.getElementById("backstage");
		refreshElements(e,hint);
	}
}

function refreshPageTitle()
{
	document.title = getPageTitle();
}

function getPageTitle()
{
	var st = wikifyPlain("SiteTitle");
	var ss = wikifyPlain("SiteSubtitle");
	return st + ((st == "" || ss == "") ? "" : " - ") + ss;
}

function refreshStyles(title,doc)
{
	setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc ? doc : document);
}

function refreshColorPalette(title)
{
	if(!startingUp)
		refreshAll();
}

function refreshAll()
{
	refreshPageTemplate();
	refreshDisplay();
	refreshStyles("StyleSheetLayout");
	refreshStyles("StyleSheetColors");
	refreshStyles(config.refresherData.styleSheet);
	refreshStyles("StyleSheetPrint");
}

//--
//-- Options stuff
//--

config.optionHandlers = {
	'txt': {
		get: function(name) {return encodeCookie(config.options[name].toString());},
		set: function(name,value) {config.options[name] = decodeCookie(value);}
	},
	'chk': {
		get: function(name) {return config.options[name] ? "true" : "false";},
		set: function(name,value) {config.options[name] = value == "true";}
	}
};

function loadOptionsCookie()
{
	if(safeMode)
		return;
	var cookies = document.cookie.split(";");
	for(var c=0; c<cookies.length; c++) {
		var p = cookies[c].indexOf("=");
		if(p != -1) {
			var name = cookies[c].substr(0,p).trim();
			var value = cookies[c].substr(p+1).trim();
			var optType = name.substr(0,3);
			if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
				config.optionHandlers[optType].set(name,value);
		}
	}
}

function saveOptionCookie(name)
{
	if(safeMode)
		return;
	var c = name + "=";
	var optType = name.substr(0,3);
	if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
		c += config.optionHandlers[optType].get(name);
	c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
	document.cookie = c;
}

function encodeCookie(s)
{
	return escape(manualConvertUnicodeToUTF8(s));
}

function decodeCookie(s)
{
	s = unescape(s);
	var re = /&#[0-9]{1,5};/g;
	return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
}


config.macros.option.genericCreate = function(place,type,opt,className,desc)
{
	var typeInfo = config.macros.option.types[type];
	var c = document.createElement(typeInfo.elementType);
	if(typeInfo.typeValue)
		c.setAttribute("type",typeInfo.typeValue);
	c[typeInfo.eventName] = typeInfo.onChange;
	c.setAttribute("option",opt);
	if(className)
		c.className = className;
	else
		c.className = typeInfo.className;
	if(config.optionsDesc[opt])
		c.setAttribute("title",config.optionsDesc[opt]);
	place.appendChild(c);
	if(desc != "no")
		createTiddlyText(place,config.optionsDesc[opt] ? config.optionsDesc[opt] : opt);
	c[typeInfo.valueField] = config.options[opt];
	return c;
};

config.macros.option.genericOnChange = function(e)
{
	var opt = this.getAttribute("option");
	if(opt) {
		var optType = opt.substr(0,3);
		var handler = config.macros.option.types[optType];
		if(handler.elementType && handler.valueField)
			config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType);
		}
	return true;
};

config.macros.option.types = {
	'txt': {
		elementType: "input",
		valueField: "value",
		eventName: "onkeyup",
		className: "txtOptionInput",
		create: config.macros.option.genericCreate,
		onChange: config.macros.option.genericOnChange
	},
	'chk': {
		elementType: "input",
		valueField: "checked",
		eventName: "onclick",
		className: "chkOptionInput",
		typeValue: "checkbox",
		create: config.macros.option.genericCreate,
		onChange: config.macros.option.genericOnChange
	}
};

config.macros.option.propagateOption = function(opt,valueField,value,elementType)
{
	config.options[opt] = value;
	saveOptionCookie(opt);
	var nodes = document.getElementsByTagName(elementType);
	for(var t=0; t<nodes.length; t++) {
		var optNode = nodes[t].getAttribute("option");
		if(opt == optNode)
			nodes[t][valueField] = value;
		}
};

config.macros.option.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	params = paramString.parseParams("anon",null,true,false,false);
	var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
	var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
	var desc = getParam(params,"desc","no");
	var type = opt.substr(0,3);
	var h = config.macros.option.types[type];
	if(h && h.create)
		h.create(place,type,opt,className,desc);
};

config.macros.options.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	params = paramString.parseParams("anon",null,true,false,false);
	var showUnknown = getParam(params,"showUnknown","no");
	var wizard = new Wizard();
	wizard.createWizard(place,this.wizardTitle);
	wizard.addStep(this.step1Title,this.step1Html);
	var markList = wizard.getElement("markList");
	var chkUnknown = wizard.getElement("chkUnknown");
	chkUnknown.checked = showUnknown == "yes";
	chkUnknown.onchange = this.onChangeUnknown;
	var listWrapper = document.createElement("div");
	markList.parentNode.insertBefore(listWrapper,markList);
	wizard.setValue("listWrapper",listWrapper);
	this.refreshOptions(listWrapper,showUnknown == "yes");
};

config.macros.options.refreshOptions = function(listWrapper,showUnknown)
{
	var opts = [];
	for(var n in config.options) {
		var opt = {};
		opt.option = "";
		opt.name = n;
		opt.lowlight = !config.optionsDesc[n];
		opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
		if(!opt.lowlight || showUnknown)
			opts.push(opt);
	}
	opts.sort(function(a,b) {return a.name.substr(3) < b.name.substr(3) ? -1 : (a.name.substr(3) == b.name.substr(3) ? 0 : +1);});
	var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
	for(n=0; n<opts.length; n++) {
		var type = opts[n].name.substr(0,3);
		var h = config.macros.option.types[type];
		if(h && h.create) {
			h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
		}
	}
};

config.macros.options.onChangeUnknown = function(e)
{
	var wizard = new Wizard(this);
	var listWrapper = wizard.getValue("listWrapper");
	removeChildren(listWrapper);
	config.macros.options.refreshOptions(listWrapper,this.checked);
	return false;
};

//--
//-- Saving
//--

var saveUsingSafari = false;

var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
var endSaveArea = '</d' + 'iv>';

// If there are unsaved changes, force the user to confirm before exitting
function confirmExit()
{
	hadConfirmExit = true;
	if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
		return config.messages.confirmExit;
}

// Give the user a chance to save changes before exitting
function checkUnsavedChanges()
{
	if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
		if(confirm(config.messages.unsavedChangesWarning))
			saveChanges();
	}
}

function updateLanguageAttribute(s)
{
	if(config.locale) {
		var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
		var m = mRE.exec(s);
		if(m) {
			var t = m[1];
			if(m[2])
				t += ' xml:lang="' + config.locale + '"';
			if(m[3])
				t += ' lang="' + config.locale + '"';
			t += ">";
			s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
		}
	}
	return s;
}

function updateMarkupBlock(s,blockName,tiddlerName)
{
	return s.replaceChunk(
			"<!--%0-START-->".format([blockName]),
			"<!--%0-END-->".format([blockName]),
			"\n" + convertUnicodeToUTF8(store.getRecursiveTiddlerText(tiddlerName,"")) + "\n");
}

function updateOriginal(original,posDiv)
{
	if(!posDiv)
		posDiv = locateStoreArea(original);
	if(!posDiv) {
		alert(config.messages.invalidFileError.format([localPath]));
		return null;
	}
	var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
				convertUnicodeToUTF8(store.allTiddlersAsHtml()) + "\n" +
				original.substr(posDiv[1]);
	var newSiteTitle = convertUnicodeToUTF8(getPageTitle()).htmlEncode();
	revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
	revised = updateLanguageAttribute(revised);
	revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
	revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
	revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
	revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
	return revised;
}

function locateStoreArea(original)
{
	// Locate the storeArea div's
	var posOpeningDiv = original.indexOf(startSaveArea);
	var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
	if(limitClosingDiv == -1)
		limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
	var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
	return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
}

function autoSaveChanges(onlyIfDirty,tiddlers)
{
	if(config.options.chkAutoSave)
		saveChanges(onlyIfDirty,tiddlers);
}

function loadOriginal(localPath)
{
	return loadFile(localPath);
}

// Save this tiddlywiki with the pending changes
function saveChanges(onlyIfDirty,tiddlers)
{
	if(onlyIfDirty && !store.isDirty())
		return;
	clearMessage();
	var t0 = new Date();
	var originalPath = document.location.toString();
	if(originalPath.substr(0,5) != "file:") {
		alert(config.messages.notFileUrlError);
		if(store.tiddlerExists(config.messages.saveInstructions))
			story.displayTiddler(null,config.messages.saveInstructions);
		return;
	}
	var localPath = getLocalPath(originalPath);
	var original = loadOriginal(localPath);
	if(original == null) {
		alert(config.messages.cantSaveError);
		if(store.tiddlerExists(config.messages.saveInstructions))
			story.displayTiddler(null,config.messages.saveInstructions);
		return;
	}
	var posDiv = locateStoreArea(original);
	if(!posDiv) {
		alert(config.messages.invalidFileError.format([localPath]));
		return;
	}
	saveMain(localPath,original,posDiv);
	if(config.options.chkSaveBackups)
		saveBackup(localPath,original);
	if(config.options.chkSaveEmptyTemplate)
		saveEmpty(localPath,original,posDiv);
	if(config.options.chkGenerateAnRssFeed)
		saveRss(localPath);
	if(config.options.chkDisplayInstrumentation)
		displayMessage("saveChanges " + (new Date()-t0) + " ms");
}

function saveMain(localPath,original,posDiv)
{
	var save;
	try {
		var revised = updateOriginal(original,posDiv);
		save = saveFile(localPath,revised);
	} catch (ex) {
		showException(ex);
	}
	if(save) {
		displayMessage(config.messages.mainSaved,"file://" + localPath);
		store.setDirty(false);
	} else {
		alert(config.messages.mainFailed);
	}
}

function saveBackup(localPath,original)
{
	var backupPath = getBackupPath(localPath);
	var backup = copyFile(backupPath,localPath);
	if(!backup)
		backup = saveFile(backupPath,original);
	if(backup)
		displayMessage(config.messages.backupSaved,"file://" + backupPath);
	else
		alert(config.messages.backupFailed);
}

function saveEmpty(localPath,original,posDiv)
{
	var emptyPath,p;
	if((p = localPath.lastIndexOf("/")) != -1)
		emptyPath = localPath.substr(0,p) + "/";
	else if((p = localPath.lastIndexOf("\\")) != -1)
		emptyPath = localPath.substr(0,p) + "\\";
	else
		emptyPath = localPath + ".";
	emptyPath += "empty.html";
	var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
	var emptySave = saveFile(emptyPath,empty);
	if(emptySave)
		displayMessage(config.messages.emptySaved,"file://" + emptyPath);
	else
		alert(config.messages.emptyFailed);
}

function saveRss(localPath)
{
	var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
	if(saveFile(rssPath,convertUnicodeToUTF8(generateRss())))
		displayMessage(config.messages.rssSaved,"file://" + rssPath);
	else
		alert(config.messages.rssFailed);
}

function getLocalPath(origPath)
{
	var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
	// Remove any location or query part of the URL
	var argPos = originalPath.indexOf("?");
	if(argPos != -1)
		originalPath = originalPath.substr(0,argPos);
	var hashPos = originalPath.indexOf("#");
	if(hashPos != -1)
		originalPath = originalPath.substr(0,hashPos);
	// Convert file://localhost/ to file:///
	if(originalPath.indexOf("file://localhost/") == 0)
		originalPath = "file://" + originalPath.substr(16);
	// Convert to a native file format
	var localPath;
	if(originalPath.charAt(9) == ":") // pc local file
		localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
		localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(7));
	else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(5));
	else // pc network file
		localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
	return localPath;
}

function getBackupPath(localPath,title,extension)
{
	var slash = "\\";
	var dirPathPos = localPath.lastIndexOf("\\");
	if(dirPathPos == -1) {
		dirPathPos = localPath.lastIndexOf("/");
		slash = "/";
	}
	var backupFolder = config.options.txtBackupFolder;
	if(!backupFolder || backupFolder == "")
		backupFolder = ".";
	var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
	backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + ".";
	if(title)
		backupPath += title.replace(/[\\\/\*\?\":<> ]/g,"_") + ".";
	backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension ? extension : "html");
	return backupPath;
}

function generateRss()
{
	var s = [];
	var d = new Date();
	var u = store.getTiddlerText("SiteUrl");
	// Assemble the header
	s.push("<" + "?xml version=\"1.0\"?" + ">");
	s.push("<rss version=\"2.0\">");
	s.push("<channel>");
	s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
	if(u)
		s.push("<link>" + u.htmlEncode() + "</link>");
	s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
	s.push("<language>en-us</language>");
	s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
	s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
	s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
	s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
	s.push("<generator>TiddlyWiki " + formatVersion() + "</generator>");
	// The body
	var tiddlers = store.getTiddlers("modified","excludeLists");
	var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
	for(var t=tiddlers.length-1; t>=n; t--) {
		s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
	}
	// And footer
	s.push("</channel>");
	s.push("</rss>");
	// Save it all
	return s.join("\n");
}


//--
//-- Filesystem code
//--

function convertUTF8ToUnicode(u)
{
	return window.netscape == undefined ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
}

function manualConvertUTF8ToUnicode(utf)
{
	var uni = utf;
	var src = 0;
	var dst = 0;
	var b1, b2, b3;
	var c;
	while(src < utf.length) {
		b1 = utf.charCodeAt(src++);
		if(b1 < 0x80) {
			dst++;
		} else if(b1 < 0xE0) {
			b2 = utf.charCodeAt(src++);
			c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
			uni = uni.substring(0,dst++).concat(c,utf.substr(src));
		} else {
			b2 = utf.charCodeAt(src++);
			b3 = utf.charCodeAt(src++);
			c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
			uni = uni.substring(0,dst++).concat(c,utf.substr(src));
		}
	}
	return uni;
}

function mozConvertUTF8ToUnicode(u)
{
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
		converter.charset = "UTF-8";
	} catch(ex) {
		return manualConvertUTF8ToUnicode(u);
	} // fallback
	var s = converter.ConvertToUnicode(u);
	var fin = converter.Finish();
	return (fin.length > 0) ? s+fin : s;
}

function convertUnicodeToUTF8(s)
{
	if(window.netscape == undefined)
		return manualConvertUnicodeToUTF8(s);
	else
		return mozConvertUnicodeToUTF8(s);
}

function manualConvertUnicodeToUTF8(s)
{
	var re = /[^\u0000-\u007F]/g ;
	return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
}

function mozConvertUnicodeToUTF8(s)
{
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
		converter.charset = "UTF-8";
	} catch(ex) {
		return manualConvertUnicodeToUTF8(s);
	} // fallback
	var u = converter.ConvertFromUnicode(s);
	var fin = converter.Finish();
	return fin.length > 0 ? u + fin : u;
}

function convertUriToUTF8(uri,charSet)
{
	if(window.netscape == undefined || charSet == undefined || charSet == "")
		return uri;
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
	} catch(ex) {
		return uri;
	}
	return converter.convertURISpecToUTF8(uri,charSet);
}

function copyFile(dest,source)
{
	return config.browser.isIE ? ieCopyFile(dest,source) : false;
}

function saveFile(fileUrl,content)
{
	var r = mozillaSaveFile(fileUrl,content);
	if(!r)
		r = ieSaveFile(fileUrl,content);
	if(!r)
		r = javaSaveFile(fileUrl,content);
	return r;
}

function loadFile(fileUrl)
{
	var r = mozillaLoadFile(fileUrl);
	if((r == null) || (r == false))
		r = ieLoadFile(fileUrl);
	if((r == null) || (r == false))
		r = javaLoadFile(fileUrl);
	return r;
}

function ieCreatePath(path)
{
	try {
		var fso = new ActiveXObject("Scripting.FileSystemObject");
	} catch(ex) {
		return null;
	}

	var pos = path.lastIndexOf("\\");
	if(pos!=-1)
		path = path.substring(0, pos+1);

	var scan = [];
	scan.push(path);
	var i = 0;
	do {
		var parent = fso.GetParentFolderName(scan[i++]);
		if(fso.FolderExists(parent))
			break;
		scan.push(parent);
	} while(true);

	for(i=scan.length-1;i>=0;i--) {
		if(!fso.FolderExists(scan[i]))
			fso.CreateFolder(scan[i]);
	}
	return true;
}

// Returns null if it can't do it, false if there's an error, true if it saved OK
function ieSaveFile(filePath,content)
{
	ieCreatePath(filePath);
	try {
		var fso = new ActiveXObject("Scripting.FileSystemObject");
	} catch(ex) {
		return null;
	}
	var file = fso.OpenTextFile(filePath,2,-1,0);
	file.Write(content);
	file.Close();
	return true;
}

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function ieLoadFile(filePath)
{
	try {
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		var file = fso.OpenTextFile(filePath,1);
		var content = file.ReadAll();
		file.Close();
	} catch(ex) {
		return null;
	}
	return content;
}

function ieCopyFile(dest,source)
{
	ieCreatePath(dest);
	try {
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		fso.GetFile(source).Copy(dest);
	} catch(ex) {
		return false;
	}
	return true;
}

// Returns null if it can't do it, false if there's an error, true if it saved OK
function mozillaSaveFile(filePath,content)
{
	if(window.Components) {
		try {
			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
			file.initWithPath(filePath);
			if(!file.exists())
				file.create(0,0664);
			var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
			out.init(file,0x20|0x02,00004,null);
			out.write(content,content.length);
			out.flush();
			out.close();
			return true;
		} catch(ex) {
			return false;
		}
	}
	return null;
}

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function mozillaLoadFile(filePath)
{
	if(window.Components) {
		try {
			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
			file.initWithPath(filePath);
			if(!file.exists())
				return null;
			var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
			inputStream.init(file,0x01,00004,null);
			var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
			sInputStream.init(inputStream);
			var contents = sInputStream.read(sInputStream.available());
			sInputStream.close();
			inputStream.close();
			return contents;
		} catch(ex) {
			return false;
		}
	}
	return null;
}

function javaUrlToFilename(url)
{
	var f = "//localhost";
	if(url.indexOf(f) == 0)
		return url.substring(f.length);
	var i = url.indexOf(":");
	if(i > 0)
		return url.substring(i-1);
	return url;
}

function javaSaveFile(filePath,content)
{
	try {
		if(document.applets["TiddlySaver"])
			return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
	} catch(ex) {
	}
	try {
		var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
		s.print(content);
		s.close();
	} catch(ex) {
		return null;
	}
	return true;
}

function javaLoadFile(filePath)
{
	try {
		if(document.applets["TiddlySaver"])
			return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
	} catch(ex) {
	}
	var content = [];
	try {
		var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
		var line;
		while((line = r.readLine()) != null)
			content.push(new String(line));
		r.close();
	} catch(ex) {
		return null;
	}
	return content.join("\n");
}

//--
//-- Server adaptor for talking to static TiddlyWiki files
//--

function FileAdaptor()
{
	this.host = null;
	this.store = null;
	return this;
}

FileAdaptor.serverType = 'file';
FileAdaptor.serverLabel = 'TiddlyWiki';

FileAdaptor.prototype.setContext = function(context,userParams,callback)
{
	if(!context) context = {};
	context.userParams = userParams;
	if(callback) context.callback = callback;
	context.adaptor = this;
	if(!context.host)
		context.host = this.host;
	context.host = FileAdaptor.fullHostName(context.host);
	if(!context.workspace)
		context.workspace = this.workspace;
	return context;
};

FileAdaptor.fullHostName = function(host)
{
	if(!host)
		return '';
	if(!host.match(/:\/\//))
		host = 'http://' + host;
	return host;
};

FileAdaptor.minHostName = function(host)
{
	return host ? host.replace(/^http:\/\//,'').replace(/\/$/,'') : '';
};

// Open the specified host
FileAdaptor.prototype.openHost = function(host,context,userParams,callback)
{
	this.host = host;
	context = this.setContext(context,userParams,callback);
	context.status = true;
	if(callback)
		window.setTimeout(function() {context.callback(context,userParams);},10);
	return true;
};

FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
{
	context.status = status;
	if(!status) {
		context.statusText = "Error reading file";
	} else {
		context.adaptor.store = new TiddlyWiki();
		if(!context.adaptor.store.importTiddlyWiki(responseText))
			context.statusText = config.messages.invalidFileError.format([url]);
	}
	context.complete(context,context.userParams);
};

// Get the list of workspaces on a given server
FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
{
	context = this.setContext(context,userParams,callback);
	context.workspaces = [{title:"(default)"}];
	context.status = true;
	if(callback)
		window.setTimeout(function() {callback(context,userParams);},10);
	return true;
};

// Open the specified workspace
FileAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
{
	this.workspace = workspace;
	context = this.setContext(context,userParams,callback);
	context.status = true;
	if(callback)
		window.setTimeout(function() {callback(context,userParams);},10);
	return true;
};

// Gets the list of tiddlers within a given workspace
FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
{
	context = this.setContext(context,userParams,callback);
	if(!context.filter)
		context.filter = filter;
	context.complete = FileAdaptor.getTiddlerListComplete;
	if(this.store) {
		var ret = context.complete(context,context.userParams);
	} else {
		ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
		if(typeof ret != "string")
			ret = true;
	}
	return ret;
};

FileAdaptor.getTiddlerListComplete = function(context,userParams)
{
	if(context.status) {
		if(context.filter) {
			context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
		} else {
			context.tiddlers = [];
			context.adaptor.store.forEachTiddler(function(title,tiddler) {context.tiddlers.push(tiddler);});
		}
		for(var i=0; i<context.tiddlers.length; i++) {
			context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
			context.tiddlers[i].fields['server.host'] = FileAdaptor.minHostName(context.host);
			context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
		}
		context.status = true;
	}
	if(context.callback) {
		window.setTimeout(function() {context.callback(context,userParams);},10);
	}
	return true;
};

FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
{
	var info = {};
	info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
	return info;
};

// Retrieve a tiddler from a given workspace on a given server
FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
{
	context = this.setContext(context,userParams,callback);
	context.title = title;
	context.complete = FileAdaptor.getTiddlerComplete;
	return context.adaptor.store ?
		context.complete(context,context.userParams) :
		loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
};

FileAdaptor.getTiddlerComplete = function(context,userParams)
{
	var t = context.adaptor.store.fetchTiddler(context.title);
	t.fields['server.type'] = FileAdaptor.serverType;
	t.fields['server.host'] = FileAdaptor.minHostName(context.host);
	t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
	context.tiddler = t;
	context.status = true;
	if(context.allowSynchronous) {
		context.isSynchronous = true;
		context.callback(context,userParams);
	} else {
		window.setTimeout(function() {context.callback(context,userParams);},10);
	}
	return true;
};

FileAdaptor.prototype.close = function()
{
	delete this.store;
	this.store = null;
};

config.adaptors[FileAdaptor.serverType] = FileAdaptor;

config.defaultAdaptor = FileAdaptor.serverType;

//--
//-- Remote HTTP requests
//--

function loadRemoteFile(url,callback,params)
{
	return doHttp("GET",url,null,null,null,null,callback,params,null);
}

// HTTP status codes
var httpStatus = {
	OK: 200,
	ContentCreated: 201,
	NoContent: 204,
	MultiStatus: 207,
	Unauthorized: 401,
	Forbidden: 403,
	NotFound: 404,
	MethodNotAllowed: 405
};

function doHttp(type,url,data,contentType,username,password,callback,params,headers,allowCache)
{
	var x = getXMLHttpRequest();
	if(!x)
		return "Can't create XMLHttpRequest object";
	x.onreadystatechange = function() {
		try {
			var status = x.status;
		} catch(ex) {
			status = false;
		}
		if(x.readyState == 4 && callback && (status !== undefined)) {
			if([0, httpStatus.OK, httpStatus.ContentCreated, httpStatus.NoContent, httpStatus.MultiStatus].contains(status))
				callback(true,params,x.responseText,url,x);
			else
				callback(false,params,null,url,x);
			x.onreadystatechange = function(){};
			x = null;
		}
	};
	if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
		window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
	try {
		if(!allowCache)
			url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
		x.open(type,url,true,username,password);
		if(data)
			x.setRequestHeader("Content-Type", contentType ? contentType : "application/x-www-form-urlencoded");
		if(x.overrideMimeType)
			x.setRequestHeader("Connection", "close");
		if(headers) {
			for(var n in headers)
				x.setRequestHeader(n,headers[n]);
		}
		x.setRequestHeader("X-Requested-With", "TiddlyWiki " + formatVersion());
		x.send(data);
	} catch(ex) {
		return exceptionText(ex);
	}
	return x;
}

function getXMLHttpRequest()
{
	try {
		var x = new XMLHttpRequest(); // Modern
	} catch(ex) {
		try {
			x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
		} catch (ex2) {
			return null;
		}
	}
	return x;
}

//--
//-- TiddlyWiki-specific utility functions
//--

formatVersion = function(v)
{
	v = v || version;
	return v.major + "." + v.minor + "." + v.revision + (v.beta ? " (beta " + v.beta + ")" : "");
};

compareVersions = function(v1,v2)
{
	var a = ["major","minor","revision"];
	for(var i = 0; i<a.length; i++) {
		var x1 = v1[a[i]] || 0;
		var x2 = v2[a[i]] || 0;
		if(x1<x2)
			return 1;
		if(x1>x2)
			return -1;
	}
	x1 = v1.beta || 9999;
	x2 = v2.beta || 9999;
	if(x1<x2)
		return 1;
	return x1 > x2 ? -1 : 0;
};

function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
{
	var btn = document.createElement("a");
	if(action) {
		btn.onclick = action;
		btn.setAttribute("href","javascript:;");
	}
	if(tooltip)
		btn.setAttribute("title",tooltip);
	if(text)
		btn.appendChild(document.createTextNode(text));
	btn.className = className ? className : "button";
	if(id)
		btn.id = id;
	if(attribs) {
		for(var n in attribs) {
			btn.setAttribute(n,attribs[n]);
		}
	}
	if(parent)
		parent.appendChild(btn);
	if(accessKey)
		btn.setAttribute("accessKey",accessKey);
	return btn;
}

function createTiddlyLink(place,title,includeText,className,isStatic,linkedFromTiddler,noToggle)
{
	var text = includeText ? title : null;
	var i = getTiddlyLinkInfo(title,className);
	var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
	btn.setAttribute("refresh","link");
	btn.setAttribute("tiddlyLink",title);
	if(noToggle)
		btn.setAttribute("noToggle","true");
	if(linkedFromTiddler) {
		var fields = linkedFromTiddler.getInheritedFields();
		if(fields)
			btn.setAttribute("tiddlyFields",fields);
	}
	return btn;
}

function refreshTiddlyLink(e,title)
{
	var i = getTiddlyLinkInfo(title,e.className);
	e.className = i.classes;
	e.title = i.subTitle;
}

function getTiddlyLinkInfo(title,currClasses)
{
	var classes = currClasses ? currClasses.split(" ") : [];
	classes.pushUnique("tiddlyLink");
	var tiddler = store.fetchTiddler(title);
	var subTitle;
	if(tiddler) {
		subTitle = tiddler.getSubtitle();
		classes.pushUnique("tiddlyLinkExisting");
		classes.remove("tiddlyLinkNonExisting");
		classes.remove("shadow");
	} else {
		classes.remove("tiddlyLinkExisting");
		classes.pushUnique("tiddlyLinkNonExisting");
		if(store.isShadowTiddler(title)) {
			subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
			classes.pushUnique("shadow");
		} else {
			subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
			classes.remove("shadow");
		}
	}
	if(typeof config.annotations[title]=="string")
		subTitle = config.annotations[title];
	return {classes: classes.join(" "),subTitle: subTitle};
}

function createExternalLink(place,url)
{
	var link = document.createElement("a");
	link.className = "externalLink";
	link.href = url;
	link.title = config.messages.externalLinkTooltip.format([url]);
	if(config.options.chkOpenInNewWindow)
		link.target = "_blank";
	place.appendChild(link);
	return link;
}

// Event handler for clicking on a tiddly link
function onClickTiddlerLink(ev)
{
	var e = ev ? ev : window.event;
	var target = resolveTarget(e);
	var link = target;
	var title = null;
	var fields = null;
	var noToggle = null;
	do {
		title = link.getAttribute("tiddlyLink");
		fields = link.getAttribute("tiddlyFields");
		noToggle = link.getAttribute("noToggle");
		link = link.parentNode;
	} while(title == null && link != null);
	if(!store.isShadowTiddler(title)) {
		var f = fields ? fields.decodeHashMap() : {};
		fields = String.encodeHashMap(merge(f,config.defaultCustomFields,true));
	}
	if(title) {
		var toggling = e.metaKey || e.ctrlKey;
		if(config.options.chkToggleLinks)
			toggling = !toggling;
		if(noToggle)
			toggling = false;
		if(store.getTiddler(title))
			fields = null;
		story.displayTiddler(target,title,null,true,null,fields,toggling);
	}
	clearMessage();
	return false;
}

// Create a button for a tag with a popup listing all the tiddlers that it tags
function createTagButton(place,tag,excludeTiddler,title,tooltip)
{
	var btn = createTiddlyButton(place,title||tag,(tooltip||config.views.wikified.tag.tooltip).format([tag]),onClickTag);
	btn.setAttribute("tag",tag);
	if(excludeTiddler)
		btn.setAttribute("tiddler",excludeTiddler);
	return btn;
}

// Event handler for clicking on a tiddler tag
function onClickTag(ev)
{
	var e = ev ? ev : window.event;
	var popup = Popup.create(this);
	var tag = this.getAttribute("tag");
	var title = this.getAttribute("tiddler");
	if(popup && tag) {
		var tagged = store.getTaggedTiddlers(tag);
		var titles = [];
		var li,r;
		for(r=0;r<tagged.length;r++) {
			if(tagged[r].title != title)
				titles.push(tagged[r].title);
		}
		var lingo = config.views.wikified.tag;
		if(titles.length > 0) {
			var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
			openAll.setAttribute("tag",tag);
			createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
			for(r=0; r<titles.length; r++) {
				createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
			}
		} else {
			createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
		}
		createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
		var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
		createTiddlyText(h,lingo.openTag.format([tag]));
	}
	Popup.show();
	e.cancelBubble = true;
	if(e.stopPropagation) e.stopPropagation();
	return false;
}

// Event handler for 'open all' on a tiddler popup
function onClickTagOpenAll(ev)
{
	var e = ev ? ev : window.event;
	var tag = this.getAttribute("tag");
	var tagged = store.getTaggedTiddlers(tag);
	story.displayTiddlers(this,tagged);
	return false;
}

function onClickError(ev)
{
	var e = ev ? ev : window.event;
	var popup = Popup.create(this);
	var lines = this.getAttribute("errorText").split("\n");
	for(var t=0; t<lines.length; t++)
		createTiddlyElement(popup,"li",null,null,lines[t]);
	Popup.show();
	e.cancelBubble = true;
	if(e.stopPropagation) e.stopPropagation();
	return false;
}

function createTiddlyDropDown(place,onchange,options,defaultValue)
{
	var sel = createTiddlyElement(place,"select");
	sel.onchange = onchange;
	for(var t=0; t<options.length; t++) {
		var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
		e.value = options[t].name;
		if(options[t].name == defaultValue)
			e.selected = true;
	}
	return sel;
}

function createTiddlyPopup(place,caption,tooltip,tiddler)
{
	if(tiddler.text) {
		createTiddlyLink(place,caption,true);
		var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
		btn.tiddler = tiddler;
	} else {
		createTiddlyText(place,caption);
	}
}

function onClickTiddlyPopup(ev)
{
	var e = ev ? ev : window.event;
	var tiddler = this.tiddler;
	if(tiddler.text) {
		var popup = Popup.create(this,"div","popupTiddler");
		wikify(tiddler.text,popup,null,tiddler);
		Popup.show();
	}
	if(e) e.cancelBubble = true;
	if(e && e.stopPropagation) e.stopPropagation();
	return false;
}

function createTiddlyError(place,title,text)
{
	var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
	if(text) btn.setAttribute("errorText",text);
}

function merge(dst,src,preserveExisting)
{
	for(p in src) {
		if(!preserveExisting || dst[p] === undefined)
			dst[p] = src[p];
	}
	return dst;
}

// Returns a string containing the description of an exception, optionally prepended by a message
function exceptionText(e,message)
{
	var s = e.description ? e.description : e.toString();
	return message ? "%0:\n%1".format([message,s]) : s;
}

// Displays an alert of an exception description with optional message
function showException(e,message)
{
	alert(exceptionText(e,message));
}

function alertAndThrow(m)
{
	alert(m);
	throw(m);
}

function glyph(name)
{
	var g = config.glyphs;
	var b = g.currBrowser;
	if(b == null) {
		b = 0;
		while(!g.browsers[b]() && b < g.browsers.length-1)
			b++;
		g.currBrowser = b;
	}
	if(!g.codes[name])
		return "";
	return g.codes[name][b];
}


//-
//- Animation engine
//-

function Animator()
{
	this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
	this.timerID = 0; // ID of the timer used for animating
	this.animations = []; // List of animations in progress
	return this;
}

// Start animation engine
Animator.prototype.startAnimating = function() //# Variable number of arguments
{
	for(var t=0; t<arguments.length; t++)
		this.animations.push(arguments[t]);
	if(this.running == 0) {
		var me = this;
		this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
	}
	this.running += arguments.length;
};

// Perform an animation engine tick, calling each of the known animation modules
Animator.prototype.doAnimate = function(me)
{
	var a = 0;
	while(a < me.animations.length) {
		var animation = me.animations[a];
		if(animation.tick()) {
			a++;
		} else {
			me.animations.splice(a,1);
			if(--me.running == 0)
				window.clearInterval(me.timerID);
		}
	}
};

Animator.slowInSlowOut = function(progress)
{
	return(1-((Math.cos(progress * Math.PI)+1)/2));
};

//--
//-- Morpher animation
//--

// Animate a set of properties of an element
function Morpher(element,duration,properties,callback)
{
	this.element = element;
	this.duration = duration;
	this.properties = properties;
	this.startTime = new Date();
	this.endTime = Number(this.startTime) + duration;
	this.callback = callback;
	this.tick();
	return this;
}

Morpher.prototype.assignStyle = function(element,style,value)
{
	switch(style) {
		case "-tw-vertScroll":
			window.scrollTo(findScrollX(),value);
			break;
		case "-tw-horizScroll":
			window.scrollTo(value,findScrollY());
			break;
		default:
			element.style[style] = value;
			break;
	}
};

Morpher.prototype.stop = function()
{
	for(var t=0; t<this.properties.length; t++) {
		var p = this.properties[t];
		if(p.atEnd !== undefined) {
			this.assignStyle(this.element,p.style,p.atEnd);
		}
	}
	if(this.callback)
		this.callback(this.element,this.properties);
};

Morpher.prototype.tick = function()
{
	var currTime = Number(new Date());
	progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
	for(var t=0; t<this.properties.length; t++) {
		var p = this.properties[t];
		if(p.start !== undefined && p.end !== undefined) {
			var template = p.template ? p.template : "%0";
			switch(p.format) {
				case undefined:
				case "style":
					var v = p.start + (p.end-p.start) * progress;
					this.assignStyle(this.element,p.style,template.format([v]));
					break;
				case "color":
					break;
			}
		}
	}
	if(currTime >= this.endTime) {
		this.stop();
		return false;
	}
	return true;
};

//--
//-- Zoomer animation
//--

function Zoomer(text,startElement,targetElement,unused)
{
	var e = createTiddlyElement(document.body,"div",null,"zoomer");
	createTiddlyElement(e,"div",null,null,text);
	var winWidth = findWindowWidth();
	var winHeight = findWindowHeight();
	var p = [
		{style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
		{style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
		{style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
		{style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
		{style: 'fontSize', start: 8, end: 24, template: '%0pt'}
	];
	var c = function(element,properties) {removeNode(element);};
	return new Morpher(e,config.animDuration,p,c);
}

//--
//-- Scroller animation
//--

function Scroller(targetElement,unused)
{
	var p = [
		{style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}
	];
	return new Morpher(targetElement,config.animDuration,p);
}

//--
//-- Slider animation
//--

// deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
function Slider(element,opening,unused,deleteMode)
{
	element.style.overflow = 'hidden';
	if(opening)
		element.style.height = '0px'; // Resolves a Firefox flashing bug
	element.style.display = 'block';
	var left = findPosX(element);
	var width = element.scrollWidth;
	var height = element.scrollHeight;
	var winWidth = findWindowWidth();
	var p = [];
	var c = null;
	if(opening) {
		p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
		p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
		p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
	} else {
		p.push({style: 'height', start: height, end: 0, template: '%0px'});
		p.push({style: 'display', atEnd: 'none'});
		p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
		p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
		switch(deleteMode) {
			case "all":
				c = function(element,properties) {removeNode(element);};
				break;
			case "children":
				c = function(element,properties) {removeChildren(element);};
				break;
		}
	}
	return new Morpher(element,config.animDuration,p,c);
}

//--
//-- Popup menu
//--

var Popup = {
	stack: [] // Array of objects with members root: and popup:
	};

Popup.create = function(root,elem,theClass)
{
	var stackPosition = this.find(root,"popup");
	Popup.remove(stackPosition+1);
	var popup = createTiddlyElement(document.body,elem ? elem : "ol","popup",theClass ? theClass : "popup");
	popup.stackPosition = stackPosition;
	Popup.stack.push({root: root, popup: popup});
	return popup;
};

Popup.onDocumentClick = function(ev)
{
	var e = ev ? ev : window.event;
	if(e.eventPhase == undefined)
		Popup.remove();
	else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
		Popup.remove();
	return true;
};

Popup.show = function(valign,halign,offset)
{
	var curr = Popup.stack[Popup.stack.length-1];
	this.place(curr.root,curr.popup,valign,halign,offset);
	addClass(curr.root,"highlight");
	if(config.options.chkAnimate && anim && typeof Scroller == "function")
		anim.startAnimating(new Scroller(curr.popup));
	else
		window.scrollTo(0,ensureVisible(curr.popup));
};

Popup.place = function(root,popup,valign,halign,offset)
{
	if(!offset)
		var offset = {x:0,y:0};
	if(popup.stackPosition >= 0 && !valign && !halign) {
		offset.x = offset.x + root.offsetWidth;
	} else {
		offset.x = (halign == 'right') ? offset.x + root.offsetWidth : offset.x;
		offset.y = (valign == 'top') ? offset.y : offset.y + root.offsetHeight;
	}
	var rootLeft = findPosX(root);
	var rootTop = findPosY(root);
	var popupLeft = rootLeft + offset.x;
	var popupTop = rootTop + offset.y;
	var winWidth = findWindowWidth();
	if(popup.offsetWidth > winWidth*0.75)
		popup.style.width = winWidth*0.75 + "px";
	var popupWidth = popup.offsetWidth;
	var scrollWidth = winWidth - document.body.offsetWidth;
	if(popupLeft + popupWidth > winWidth - scrollWidth - 1) {
		if(halign == 'right')
			popupLeft = popupLeft - root.offsetWidth - popupWidth;
		else
			popupLeft = winWidth - popupWidth - scrollWidth - 1;
	}
	popup.style.left = popupLeft + "px";
	popup.style.top = popupTop + "px";
	popup.style.display = "block";
};

Popup.find = function(e)
{
	var pos = -1;
	for (var t=this.stack.length-1; t>=0; t--) {
		if(isDescendant(e,this.stack[t].popup))
			pos = i;
	}
	return pos;
};

Popup.remove = function(pos)
{
	if(!pos) var pos = 0;
	if(Popup.stack.length > pos) {
		Popup.removeFrom(pos);
	}
};

Popup.removeFrom = function(from)
{
	for(var t=Popup.stack.length-1; t>=from; t--) {
		var p = Popup.stack[t];
		removeClass(p.root,"highlight");
		removeNode(p.popup);
	}
	Popup.stack = Popup.stack.slice(0,from);
};

//--
//-- Wizard support
//--

function Wizard(elem)
{
	if(elem) {
		this.formElem = findRelated(elem,"wizard","className");
		this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
		this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
	} else {
		this.formElem = null;
		this.bodyElem = null;
		this.footElem = null;
	}
}

Wizard.prototype.setValue = function(name,value)
{
	if(this.formElem)
		this.formElem[name] = value;
};

Wizard.prototype.getValue = function(name)
{
	return this.formElem ? this.formElem[name] : null;
};

Wizard.prototype.createWizard = function(place,title)
{
	this.formElem = createTiddlyElement(place,"form",null,"wizard");
	createTiddlyElement(this.formElem,"h1",null,null,title);
	this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
	this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
};

Wizard.prototype.clear = function()
{
	removeChildren(this.bodyElem);
};

Wizard.prototype.setButtons = function(buttonInfo,status)
{
	removeChildren(this.footElem);
	for(var t=0; t<buttonInfo.length; t++) {
		createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
		insertSpacer(this.footElem);
		}
	if(typeof status == "string") {
		createTiddlyElement(this.footElem,"span",null,"status",status);
	}
};

Wizard.prototype.addStep = function(stepTitle,html)
{
	removeChildren(this.bodyElem);
	var w = createTiddlyElement(this.bodyElem,"div");
	createTiddlyElement(w,"h2",null,null,stepTitle);
	var step = createTiddlyElement(w,"div",null,"wizardStep");
	step.innerHTML = html;
	applyHtmlMacros(step,tiddler);
};

Wizard.prototype.getElement = function(name)
{
	return this.formElem.elements[name];
};

//--
//-- ListView gadget
//--

var ListView = {};

// Create a listview
ListView.create = function(place,listObject,listTemplate,callback,className)
{
	var table = createTiddlyElement(place,"table",null,className ? className : "listView twtable");
	var thead = createTiddlyElement(table,"thead");
	var r = createTiddlyElement(thead,"tr");
	for(var t=0; t<listTemplate.columns.length; t++) {
		var columnTemplate = listTemplate.columns[t];
		var c = createTiddlyElement(r,"th");
		var colType = ListView.columnTypes[columnTemplate.type];
		if(colType && colType.createHeader)
			colType.createHeader(c,columnTemplate,t);
	}
	var tbody = createTiddlyElement(table,"tbody");
	for(var rc=0; rc<listObject.length; rc++) {
		rowObject = listObject[rc];
		r = createTiddlyElement(tbody,"tr");
		for(c=0; c<listTemplate.rowClasses.length; c++) {
			if(rowObject[listTemplate.rowClasses[c].field])
				addClass(r,listTemplate.rowClasses[c].className);
		}
		rowObject.rowElement = r;
		rowObject.colElements = {};
		for(var cc=0; cc<listTemplate.columns.length; cc++) {
			c = createTiddlyElement(r,"td");
			columnTemplate = listTemplate.columns[cc];
			var field = columnTemplate.field;
			colType = ListView.columnTypes[columnTemplate.type];
			if(colType && colType.createItem)
				colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
			rowObject.colElements[field] = c;
		}
	}
	if(callback && listTemplate.actions)
		createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
	if(callback && listTemplate.buttons) {
		for(t=0; t<listTemplate.buttons.length; t++) {
			var a = listTemplate.buttons[t];
			if(a && a.name != "")
				createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
		}
	}
	return table;
};

ListView.getCommandHandler = function(callback,name,allowEmptySelection)
{
	return function(e) {
		var view = findRelated(this,"TABLE",null,"previousSibling");
		var tiddlers = [];
		ListView.forEachSelector(view,function(e,rowName) {
					if(e.checked)
						tiddlers.push(rowName);
					});
		if(tiddlers.length == 0 && !allowEmptySelection) {
			alert(config.messages.nothingSelected);
		} else {
			if(this.nodeName.toLowerCase() == "select") {
				callback(view,this.value,tiddlers);
				this.selectedIndex = 0;
			} else {
				callback(view,name,tiddlers);
			}
		}
	};
};

// Invoke a callback for each selector checkbox in the listview
ListView.forEachSelector = function(view,callback)
{
	var checkboxes = view.getElementsByTagName("input");
	var hadOne = false;
	for(var t=0; t<checkboxes.length; t++) {
		var cb = checkboxes[t];
		if(cb.getAttribute("type") == "checkbox") {
			var rn = cb.getAttribute("rowName");
			if(rn) {
				callback(cb,rn);
				hadOne = true;
			}
		}
	}
	return hadOne;
};

ListView.getSelectedRows = function(view)
{
	var rowNames = [];
	ListView.forEachSelector(view,function(e,rowName) {
				if(e.checked)
					rowNames.push(rowName);
				});
	return rowNames;
};

ListView.columnTypes = {};

ListView.columnTypes.String = {
	createHeader: function(place,columnTemplate,col)
		{
			createTiddlyText(place,columnTemplate.title);
		},
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			if(v != undefined)
				createTiddlyText(place,v);
		}
};

ListView.columnTypes.WikiText = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			if(v != undefined)
				wikify(v,place,null,null);
		}
};

ListView.columnTypes.Tiddler = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			if(v != undefined && v.title)
				createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
		}
};

ListView.columnTypes.Size = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			if(v != undefined) {
				var t = 0;
				while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
					t++;
				createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
			}
		}
};

ListView.columnTypes.Link = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			var c = columnTemplate.text;
			if(v != undefined)
				createTiddlyText(createExternalLink(place,v),c ? c : v);
		}
};

ListView.columnTypes.Date = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			if(v != undefined)
				createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
		}
};

ListView.columnTypes.StringList = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			if(v != undefined) {
				for(var t=0; t<v.length; t++) {
					createTiddlyText(place,v[t]);
					createTiddlyElement(place,"br");
				}
			}
		}
};

ListView.columnTypes.Selector = {
	createHeader: function(place,columnTemplate,col)
		{
			createTiddlyCheckbox(place,null,false,this.onHeaderChange);
		},
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var e = createTiddlyCheckbox(place,null,listObject[field],null);
			e.setAttribute("rowName",listObject[columnTemplate.rowName]);
		},
	onHeaderChange: function(e)
		{
			var state = this.checked;
			var view = findRelated(this,"TABLE");
			if(!view)
				return;
			ListView.forEachSelector(view,function(e,rowName) {
								e.checked = state;
							});
		}
};

ListView.columnTypes.Tags = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var tags = listObject[field];
			createTiddlyText(place,String.encodeTiddlyLinkList(tags));
		}
};

ListView.columnTypes.Boolean = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			if(listObject[field] == true)
				createTiddlyText(place,columnTemplate.trueText);
			if(listObject[field] == false)
				createTiddlyText(place,columnTemplate.falseText);
		}
};

ListView.columnTypes.TagCheckbox = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
			e.setAttribute("tiddler",listObject.title);
			e.setAttribute("tag",columnTemplate.tag);
		},
	onChange : function(e)
		{
			var tag = this.getAttribute("tag");
			var tiddler = this.getAttribute("tiddler");
			store.setTiddlerTag(tiddler,this.checked,tag);
		}
};

ListView.columnTypes.TiddlerLink = {
	createHeader: ListView.columnTypes.String.createHeader,
	createItem: function(place,listObject,field,columnTemplate,col,row)
		{
			var v = listObject[field];
			if(v != undefined) {
				var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
				createTiddlyText(link,listObject[field]);
			}
		}
};

//--
//-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
//--

// Clamp a number to a range
Number.prototype.clamp = function(min,max)
{
	var c = this;
	if(c < min)
		c = min;
	if(c > max)
		c = max;
	return c;
};

// Add indexOf function if browser does not support it
if(!Array.indexOf) {
Array.prototype.indexOf = function(item,from)
{
	if(!from)
		from = 0;
	for(var i=from; i<this.length; i++) {
		if(this[i] === item)
			return i;
	}
	return -1;
};}

// Find an entry in a given field of the members of an array
Array.prototype.findByField = function(field,value)
{
	for(var t=0; t<this.length; t++) {
		if(this[t][field] == value)
			return t;
	}
	return null;
};

// Return whether an entry exists in an array
Array.prototype.contains = function(item)
{
	return this.indexOf(item) != -1;
};

// Adds, removes or toggles a particular value within an array
//  value - value to add
//  mode - +1 to add value, -1 to remove value, 0 to toggle it
Array.prototype.setItem = function(value,mode)
{
	var p = this.indexOf(value);
	if(mode == 0)
		mode = (p == -1) ? +1 : -1;
	if(mode == +1) {
		if(p == -1)
			this.push(value);
	} else if(mode == -1) {
		if(p != -1)
			this.splice(p,1);
	}
};

// Return whether one of a list of values exists in an array
Array.prototype.containsAny = function(items)
{
	for(var i=0; i<items.length; i++) {
		if(this.indexOf(items[i]) != -1)
			return true;
	}
	return false;
};

// Return whether all of a list of values exists in an array
Array.prototype.containsAll = function(items)
{
	for(var i = 0; i<items.length; i++) {
		if(this.indexOf(items[i]) == -1)
			return false;
	}
	return true;
};

// Push a new value into an array only if it is not already present in the array. If the optional unique parameter is false, it reverts to a normal push
Array.prototype.pushUnique = function(item,unique)
{
	if(unique === false) {
		this.push(item);
	} else {
		if(this.indexOf(item) == -1)
			this.push(item);
	}
};

Array.prototype.remove = function(item)
{
	var p = this.indexOf(item);
	if(p != -1)
		this.splice(p,1);
};

if(!Array.prototype.map) {
Array.prototype.map = function(fn,thisObj)
{
	var scope = thisObj || window;
	var a = [];
	for(var i=0, j=this.length; i < j; ++i) {
		a.push(fn.call(scope,this[i],i,this));
	}
	return a;
};}

// Get characters from the right end of a string
String.prototype.right = function(n)
{
	return n < this.length ? this.slice(this.length-n) : this;
};

// Trim whitespace from both ends of a string
String.prototype.trim = function()
{
	return this.replace(/^\s*|\s*$/g,"");
};

// Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
String.prototype.unDash = function()
{
	var s = this.split("-");
	if(s.length > 1) {
		for(var t=1; t<s.length; t++)
			s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
	}
	return s.join("");
};

// Substitute substrings from an array into a format string that includes '%1'-type specifiers
String.prototype.format = function(substrings)
{
	var subRegExp = /(?:%(\d+))/mg;
	var currPos = 0;
	var r = [];
	do {
		var match = subRegExp.exec(this);
		if(match && match[1]) {
			if(match.index > currPos)
				r.push(this.substring(currPos,match.index));
			r.push(substrings[parseInt(match[1])]);
			currPos = subRegExp.lastIndex;
		}
	} while(match);
	if(currPos < this.length)
		r.push(this.substring(currPos,this.length));
	return r.join("");
};

// Escape any special RegExp characters with that character preceded by a backslash
String.prototype.escapeRegExp = function()
{
	var s = "\\^$*+?()=!|,{}[].";
	var c = this;
	for(var t=0; t<s.length; t++)
		c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
	return c;
};

// Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
String.prototype.escapeLineBreaks = function()
{
	return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
};

// Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
String.prototype.unescapeLineBreaks = function()
{
	return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
};

// Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
String.prototype.htmlEncode = function()
{
	return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
};

// Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
String.prototype.htmlDecode = function()
{
	return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
};

// Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
String.prototype.toJSONString = function()
{
	var m = {
		'\b': '\\b',
		'\f': '\\f',
		'\n': '\\n',
		'\r': '\\r',
		'\t': '\\t',
		'"' : '\\"',
		'\\': '\\\\'
		};
	var replaceFn = function(a,b) {
		var c = m[b];
		if(c)
			return c;
		c = b.charCodeAt();
		return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
		};
	if(/["\\\x00-\x1f]/.test(this))
		return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
	return '"' + this + '"';
};

// Parse a space-separated string of name:value parameters
// The result is an array of objects:
//   result[0] = object with a member for each parameter name, value of that member being an array of values
//   result[1..n] = one object for each parameter, with 'name' and 'value' members
String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
{
	var parseToken = function(match,p) {
		var n;
		if(match[p]) // Double quoted
			n = match[p];
		else if(match[p+1]) // Single quoted
			n = match[p+1];
		else if(match[p+2]) // Double-square-bracket quoted
			n = match[p+2];
		else if(match[p+3]) // Double-brace quoted
			try {
				n = match[p+3];
				if(allowEval)
					n = window.eval(n);
			} catch(ex) {
				throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
			}
		else if(match[p+4]) // Unquoted
			n = match[p+4];
		else if(match[p+5]) // empty quote
			n = "";
		return n;
	};
	var r = [{}];
	var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
	var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
	var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
	var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
	var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
	var emptyQuote = "((?:\"\")|(?:''))";
	var skipSpace = "(?:\\s*)";
	var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
	var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
	var params = [];
	do {
		var match = re.exec(this);
		if(match) {
			var n = parseToken(match,1);
			if(noNames) {
				r.push({name:"",value:n});
			} else {
				var v = parseToken(match,8);
				if(v == null && defaultName) {
					v = n;
					n = defaultName;
				} else if(v == null && defaultValue) {
					v = defaultValue;
				}
				r.push({name:n,value:v});
				if(cascadeDefaults) {
					defaultName = n;
					defaultValue = v;
				}
			}
		}
	} while(match);
	// Summarise parameters into first element
	for(var t=1; t<r.length; t++) {
		if(r[0][r[t].name])
			r[0][r[t].name].push(r[t].value);
		else
			r[0][r[t].name] = [r[t].value];
	}
	return r;
};

// Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
// [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
// an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
String.prototype.readMacroParams = function()
{
	var p = this.parseParams("list",null,true,true);
	var n = [];
	for(var t=1; t<p.length; t++)
		n.push(p[t].value);
	return n;
};

// Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
String.prototype.readBracketedList = function(unique)
{
	var p = this.parseParams("list",null,false,true);
	var n = [];
	for(var t=1; t<p.length; t++) {
		if(p[t].value)
			n.pushUnique(p[t].value,unique);
	}
	return n;
};

// Returns array with start and end index of chunk between given start and end marker, or undefined.
String.prototype.getChunkRange = function(start,end)
{
	var s = this.indexOf(start);
	if(s != -1) {
		s += start.length;
		var e = this.indexOf(end,s);
		if(e != -1)
			return [s,e];
	}
};

// Replace a chunk of a string given start and end markers
String.prototype.replaceChunk = function(start,end,sub)
{
	var r = this.getChunkRange(start,end);
	return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
};

// Returns a chunk of a string between start and end markers, or undefined
String.prototype.getChunk = function(start,end)
{
	var r = this.getChunkRange(start,end);
	if(r)
		return this.substring(r[0],r[1]);
};


// Static method to bracket a string with double square brackets if it contains a space
String.encodeTiddlyLink = function(title)
{
	return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
};

// Static method to encodeTiddlyLink for every item in an array and join them with spaces
String.encodeTiddlyLinkList = function(list)
{
	if(list) {
		var results = [];
		for(var t=0; t<list.length; t++)
			results.push(String.encodeTiddlyLink(list[t]));
		return results.join(" ");
	} else {
		return "";
	}
};

// Convert a string as a sequence of name:"value" pairs into a hashmap
String.prototype.decodeHashMap = function()
{
	var fields = this.parseParams("anon","",false);
	var r = {};
	for(var t=1; t<fields.length; t++)
		r[fields[t].name] = fields[t].value;
	return r;
};

// Static method to encode a hashmap into a name:"value"... string
String.encodeHashMap = function(hashmap)
{
	var r = [];
	for(var t in hashmap)
		r.push(t + ':"' + hashmap[t] + '"');
	return r.join(" ");
};

// Static method to left-pad a string with 0s to a certain width
String.zeroPad = function(n,d)
{
	var s = n.toString();
	if(s.length < d)
		s = "000000000000000000000000000".substr(0,d-s.length) + s;
	return s;
};

String.prototype.startsWith = function(prefix)
{
	return !prefix || this.substring(0,prefix.length) == prefix;
};

// Returns the first value of the given named parameter.
function getParam(params,name,defaultValue)
{
	if(!params)
		return defaultValue;
	var p = params[0][name];
	return p ? p[0] : defaultValue;
}

// Returns the first value of the given boolean named parameter.
function getFlag(params,name,defaultValue)
{
	return !!getParam(params,name,defaultValue);
}

// Substitute date components into a string
Date.prototype.formatString = function(template)
{
	var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
	t = t.replace(/hh12/g,this.getHours12());
	t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
	t = t.replace(/hh/g,this.getHours());
	t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
	t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
	t = t.replace(/mm/g,this.getMinutes());
	t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
	t = t.replace(/ss/g,this.getSeconds());
	t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
	t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
	t = t.replace(/wYYYY/g,this.getYearForWeekNo());
	t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
	t = t.replace(/YYYY/g,this.getFullYear());
	t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
	t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
	t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
	t = t.replace(/MM/g,this.getMonth()+1);
	t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
	t = t.replace(/WW/g,this.getWeek());
	t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
	t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
	t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
	t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
	t = t.replace(/DD/g,this.getDate());
	var tz = this.getTimezoneOffset();
	var atz = Math.abs(tz);
	t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + String.zeroPad(Math.floor(atz / 60),2) + ':' + String.zeroPad(atz % 60,2));
	return t;
};

Date.prototype.getWeek = function()
{
	var dt = new Date(this.getTime());
	var d = dt.getDay();
	if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
	dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
	var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
	return Math.floor(n/7)+1;
};

Date.prototype.getYearForWeekNo = function()
{
	var dt = new Date(this.getTime());
	var d = dt.getDay();
	if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
	dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
	return dt.getFullYear();
};

Date.prototype.getHours12 = function()
{
	var h = this.getHours();
	return h > 12 ? h-12 : ( h > 0 ? h : 12 );
};

Date.prototype.getAmPm = function()
{
	return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
};

Date.prototype.daySuffix = function()
{
	return config.messages.dates.daySuffixes[this.getDate()-1];
};

// Convert a date to local YYYYMMDDHHMM string format
Date.prototype.convertToLocalYYYYMMDDHHMM = function()
{
	return this.getFullYear() + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2);
};

// Convert a date to UTC YYYYMMDDHHMM string format
Date.prototype.convertToYYYYMMDDHHMM = function()
{
	return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2);
};

// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
{
	return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + "." + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2) + String.zeroPad(this.getUTCSeconds(),2) + String.zeroPad(this.getUTCMilliseconds(),4);
};

// Static method to create a date from a UTC YYYYMMDDHHMM format string
Date.convertFromYYYYMMDDHHMM = function(d)
{
	var hh = d.substr(8,2) || "00";
	var mm = d.substr(10,2) || "00";
	return new Date(Date.UTC(parseInt(d.substr(0,4),10),
			parseInt(d.substr(4,2),10)-1,
			parseInt(d.substr(6,2),10),
			parseInt(hh,10),
			parseInt(mm,10),0,0));
};


//--
//-- Crypto functions and associated conversion routines
//--

// Crypto 'namespace'
function Crypto() {}

// Convert a string to an array of big-endian 32-bit words
Crypto.strToBe32s = function(str)
{
	var be=Array();
	var len=Math.floor(str.length/4);
	var i, j;
	for(i=0, j=0; i<len; i++, j+=4) {
		be[i]=((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
	}
	while(j<str.length) {
		be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
		j++;
	}
	return be;
};

// Convert an array of big-endian 32-bit words to a string
Crypto.be32sToStr = function(be)
{
	var str='';
	for(var i=0;i<be.length*32;i+=8)
		str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
	return str;
};

// Convert an array of big-endian 32-bit words to a hex string
Crypto.be32sToHex = function(be)
{
	var hex='0123456789ABCDEF';
	var str='';
	for(var i=0;i<be.length*4;i++)
		str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
	return str;
};

// Return, in hex, the SHA-1 hash of a string
Crypto.hexSha1Str = function(str)
{
	return Crypto.be32sToHex(Crypto.sha1Str(str));
};

// Return the SHA-1 hash of a string
Crypto.sha1Str = function(str)
{
	return Crypto.sha1(Crypto.strToBe32s(str),str.length);
};

// Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
Crypto.sha1 = function(x,blen)
{
	// Add 32-bit integers, wrapping at 32 bits
	function add32(a,b)
	{
		var lsw=(a&0xFFFF)+(b&0xFFFF);
		var msw=(a>>16)+(b>>16)+(lsw>>16);
		return (msw<<16)|(lsw&0xFFFF);
	}
	function AA(a,b,c,d,e)
	{
		b=(b>>>27)|(b<<5);
		var lsw=(a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
		var msw=(a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
		return (msw<<16)|(lsw&0xFFFF);
	}
	function RR(w,j)
	{
		var n=w[j-3]^w[j-8]^w[j-14]^w[j-16];
		return (n>>>31)|(n<<1);
	}

	var len=blen*8;
	x[len>>5] |= 0x80 << (24-len%32);
	x[((len+64>>9)<<4)+15]=len;
	var w=Array(80);

	var k1=0x5A827999;
	var k2=0x6ED9EBA1;
	var k3=0x8F1BBCDC;
	var k4=0xCA62C1D6;

	var h0=0x67452301;
	var h1=0xEFCDAB89;
	var h2=0x98BADCFE;
	var h3=0x10325476;
	var h4=0xC3D2E1F0;

	for(var i=0;i<x.length;i+=16) {
		var j=0;
		var t;
		var a=h0;
		var b=h1;
		var c=h2;
		var d=h3;
		var e=h4;
		while(j<16) {
			w[j]=x[i+j];
			t=AA(e,a,d^(b&(c^d)),w[j],k1);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
		}
		while(j<20) {
			w[j]=RR(w,j);
			t=AA(e,a,d^(b&(c^d)),w[j],k1);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
		}
		while(j<40) {
			w[j]=RR(w,j);
			t=AA(e,a,b^c^d,w[j],k2);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
		}
		while(j<60) {
			w[j]=RR(w,j);
			t=AA(e,a,(b&c)|(d&(b|c)),w[j],k3);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
		}
		while(j<80) {
			w[j]=RR(w,j);
			t=AA(e,a,b^c^d,w[j],k4);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
		}
		h0=add32(h0,a);
		h1=add32(h1,b);
		h2=add32(h2,c);
		h3=add32(h3,d);
		h4=add32(h4,e);
	}
	return [h0,h1,h2,h3,h4];
};

//--
//-- RGB colour object
//--

// Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
function RGB(r,g,b)
{
	this.r = 0;
	this.g = 0;
	this.b = 0;
	if(typeof r == "string") {
		if(r.substr(0,1) == "#") {
			if(r.length == 7) {
				this.r = parseInt(r.substr(1,2),16)/255;
				this.g = parseInt(r.substr(3,2),16)/255;
				this.b = parseInt(r.substr(5,2),16)/255;
			} else {
				this.r = parseInt(r.substr(1,1),16)/15;
				this.g = parseInt(r.substr(2,1),16)/15;
				this.b = parseInt(r.substr(3,1),16)/15;
			}
		} else {
			var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
			var c = r.match(rgbPattern);
			if(c) {
				this.r = parseInt(c[1],10)/255;
				this.g = parseInt(c[2],10)/255;
				this.b = parseInt(c[3],10)/255;
			}
		}
	} else {
		this.r = r;
		this.g = g;
		this.b = b;
	}
	return this;
}

// Mixes this colour with another in a specified proportion
// c = other colour to mix
// f = 0..1 where 0 is this colour and 1 is the new colour
// Returns an RGB object
RGB.prototype.mix = function(c,f)
{
	return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
};

// Return an rgb colour as a #rrggbb format hex string
RGB.prototype.toString = function()
{
	return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
				 ("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
				 ("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
};

//--
//-- DOM utilities - many derived from www.quirksmode.org
//--

function drawGradient(place,horiz,locolors,hicolors)
{
	if(!hicolors)
		hicolors = locolors;
	for(var t=0; t<= 100; t+=2) {
		var bar = document.createElement("div");
		place.appendChild(bar);
		bar.style.position = "absolute";
		bar.style.left = horiz ? t + "%" : 0;
		bar.style.top = horiz ? 0 : t + "%";
		bar.style.width = horiz ? (101-t) + "%" : "100%";
		bar.style.height = horiz ? "100%" : (101-t) + "%";
		bar.style.zIndex = -1;
		var p = t/100*(locolors.length-1);
		bar.style.backgroundColor = hicolors[Math.floor(p)].mix(locolors[Math.ceil(p)],p-Math.floor(p)).toString();
	}
}

function createTiddlyText(parent,text)
{
	return parent.appendChild(document.createTextNode(text));
}

function createTiddlyCheckbox(parent,caption,checked,onChange)
{
	var cb = document.createElement("input");
	cb.setAttribute("type","checkbox");
	cb.onclick = onChange;
	parent.appendChild(cb);
	cb.checked = checked;
	cb.className = "chkOptionInput";
	if(caption)
		wikify(caption,parent);
	return cb;
}

function createTiddlyElement(parent,element,id,className,text,attribs)
{
	var e = document.createElement(element);
	if(className != null)
		e.className = className;
	if(id != null)
		e.setAttribute("id",id);
	if(text != null)
		e.appendChild(document.createTextNode(text));
	if(attribs) {
		for(var n in attribs) {
			e.setAttribute(n,attribs[n]);
		}
	}
	if(parent != null)
		parent.appendChild(e);
	return e;
}

function addEvent(obj,type,fn)
{
	if(obj.attachEvent) {
		obj['e'+type+fn] = fn;
		obj[type+fn] = function(){obj['e'+type+fn](window.event);};
		obj.attachEvent('on'+type,obj[type+fn]);
	} else {
		obj.addEventListener(type,fn,false);
	}
}

function removeEvent(obj,type,fn)
{
	if(obj.detachEvent) {
		obj.detachEvent('on'+type,obj[type+fn]);
		obj[type+fn] = null;
	} else {
		obj.removeEventListener(type,fn,false);
	}
}

function addClass(e,className)
{
	var currClass = e.className.split(" ");
	if(currClass.indexOf(className) == -1)
		e.className += " " + className;
}

function removeClass(e,className)
{
	var currClass = e.className.split(" ");
	var i = currClass.indexOf(className);
	while(i != -1) {
		currClass.splice(i,1);
		i = currClass.indexOf(className);
	}
	e.className = currClass.join(" ");
}

function hasClass(e,className)
{
	if(e.className) {
		if(e.className.split(" ").indexOf(className) != -1)
			return true;
	}
	return false;
}

// Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
function findRelated(e,value,name,relative)
{
	name = name ? name : "tagName";
	relative = relative ? relative : "parentNode";
	if(name == "className") {
		while(e && !hasClass(e,value)) {
			e = e[relative];
		}
	} else {
		while(e && e[name] != value) {
			e = e[relative];
		}
	}
	return e;
}

// Resolve the target object of an event
function resolveTarget(e)
{
	var obj;
	if(e.target)
		obj = e.target;
	else if(e.srcElement)
		obj = e.srcElement;
	if(obj.nodeType == 3) // defeat Safari bug
		obj = obj.parentNode;
	return obj;
}

// Prevent an event from bubbling
function stopEvent(e)
{
	var ev = e ? e : window.event;
	ev.cancelBubble = true;
	if(ev.stopPropagation) ev.stopPropagation();
	return false;
}

// Return the content of an element as plain text with no formatting
function getPlainText(e)
{
	var text = "";
	if(e.innerText)
		text = e.innerText;
	else if(e.textContent)
		text = e.textContent;
	return text;
}

// Get the scroll position for window.scrollTo necessary to scroll a given element into view
function ensureVisible(e)
{
	var posTop = findPosY(e);
	var posBot = posTop + e.offsetHeight;
	var winTop = findScrollY();
	var winHeight = findWindowHeight();
	var winBot = winTop + winHeight;
	if(posTop < winTop) {
		return posTop;
	} else if(posBot > winBot) {
		if(e.offsetHeight < winHeight)
			return posTop - (winHeight - e.offsetHeight);
		else
			return posTop;
	} else {
		return winTop;
	}
}

// Get the current width of the display window
function findWindowWidth()
{
	return window.innerWidth ? window.innerWidth : document.documentElement.clientWidth;
}

// Get the current height of the display window
function findWindowHeight()
{
	return window.innerHeight ? window.innerHeight : document.documentElement.clientHeight;
}

// Get the current horizontal page scroll position
function findScrollX()
{
	return window.scrollX ? window.scrollX : document.documentElement.scrollLeft;
}

// Get the current vertical page scroll position
function findScrollY()
{
	return window.scrollY ? window.scrollY : document.documentElement.scrollTop;
}

function findPosX(obj)
{
	var curleft = 0;
	while(obj.offsetParent) {
		curleft += obj.offsetLeft;
		obj = obj.offsetParent;
	}
	return curleft;
}

function findPosY(obj)
{
	var curtop = 0;
	while(obj.offsetParent) {
		curtop += obj.offsetTop;
		obj = obj.offsetParent;
	}
	return curtop;
}

// Blur a particular element
function blurElement(e)
{
	if(e != null && e.focus && e.blur) {
		e.focus();
		e.blur();
	}
}

// Create a non-breaking space
function insertSpacer(place)
{
	var e = document.createTextNode(String.fromCharCode(160));
	if(place)
		place.appendChild(e);
	return e;
}

// Remove all children of a node
function removeChildren(e)
{
	while(e && e.hasChildNodes())
		removeNode(e.firstChild);
}

// Remove a node and all it's children
function removeNode(e)
{
	scrubNode(e);
	e.parentNode.removeChild(e);
}

// Remove any event handlers or non-primitve custom attributes
function scrubNode(e)
{
	if(!config.browser.isIE)
		return;
	var att = e.attributes;
	if(att) {
		for(var t=0; t<att.length; t++) {
			var n = att[t].name;
			if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
				try {
					e[n] = null;
				} catch(ex) {
				}
			}
		}
	}
	var c = e.firstChild;
	while(c) {
		scrubNode(c);
		c = c.nextSibling;
	}
}

// Add a stylesheet, replacing any previous custom stylesheet
function setStylesheet(s,id,doc)
{
	if(!id)
		id = "customStyleSheet";
	if(!doc)
		doc = document;
	var n = doc.getElementById(id);
	if(doc.createStyleSheet) {
		// Test for IE's non-standard createStyleSheet method
		if(n)
			n.parentNode.removeChild(n);
		// This failed without the &nbsp;
		doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
	} else {
		if(n) {
			n.replaceChild(doc.createTextNode(s),n.firstChild);
		} else {
			n = doc.createElement("style");
			n.type = "text/css";
			n.id = id;
			n.appendChild(doc.createTextNode(s));
			doc.getElementsByTagName("head")[0].appendChild(n);
		}
	}
}

function removeStyleSheet(id)
{
	var e = document.getElementById(id);
	if(e)
		e.parentNode.removeChild(e);
}

// Force the browser to do a document reflow when needed to workaround browser bugs
function forceReflow()
{
	if(config.browser.isGecko) {
		setStylesheet("body {top:0px;margin-top:0px;}","forceReflow");
		setTimeout(function() {setStylesheet("","forceReflow");},1);
	}
}

// Replace the current selection of a textarea or text input and scroll it into view
function replaceSelection(e,text)
{
	if(e.setSelectionRange) {
		var oldpos = e.selectionStart;
		var isRange = e.selectionEnd > e.selectionStart;
		e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
		e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
		var linecount = e.value.split('\n').length;
		var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
		e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
	} else if(document.selection) {
		var range = document.selection.createRange();
		if(range.parentElement() == e) {
			var isCollapsed = range.text == "";
			range.text = text;
			if(!isCollapsed) {
				range.moveStart('character', -text.length);
				range.select();
			}
		}
	}
}

// Returns the text of the given (text) node, possibly merging subsequent text nodes
function getNodeText(e)
{
	var t = "";
	while(e && e.nodeName == "#text") {
		t += e.nodeValue;
		e = e.nextSibling;
	}
	return t;
}

// Returns true if the element e has a given ancestor element
function isDescendant(e,ancestor)
{
	while(e) {
		if(e === ancestor)
			return true;
		e = e.parentNode;
	}
	return false;
}

//--
//-- LoaderBase and SaverBase
//--

function LoaderBase() {}

LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
{
	var title = this.getTitle(store,node);
	if(safeMode && store.isShadowTiddler(title))
		return;
	if(title) {
		var tiddler = store.createTiddler(title);
		this.internalizeTiddler(store,tiddler,title,node);
		tiddlers.push(tiddler);
	}
};

LoaderBase.prototype.loadTiddlers = function(store,nodes)
{
	var tiddlers = [];
	for(var t = 0; t < nodes.length; t++) {
		try {
			this.loadTiddler(store,nodes[t],tiddlers);
		} catch(ex) {
			showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
		}
	}
	return tiddlers;
};

function SaverBase() {}

SaverBase.prototype.externalize = function(store)
{
	var results = [];
	var tiddlers = store.getTiddlers("title");
	for(var t = 0; t < tiddlers.length; t++) {
		if(!tiddlers[t].doNotSave())
			results.push(this.externalizeTiddler(store, tiddlers[t]));
	}
	return results.join("\n");
};

//--
//-- TW21Loader (inherits from LoaderBase)
//--

function TW21Loader() {}

TW21Loader.prototype = new LoaderBase();

TW21Loader.prototype.getTitle = function(store,node)
{
	var title = null;
	if(node.getAttribute) {
		title = node.getAttribute("title");
		if(!title)
			title = node.getAttribute("tiddler");
	}
	if(!title && node.id) {
		var lenPrefix = store.idPrefix.length;
		if(node.id.substr(0,lenPrefix) == store.idPrefix)
			title = node.id.substr(lenPrefix);
	}
	return title;
};

TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
{
	var e = node.firstChild;
	var text = null;
	if(node.getAttribute("tiddler")) {
		text = getNodeText(e).unescapeLineBreaks();
	} else {
		while(e.nodeName!="PRE" && e.nodeName!="pre") {
			e = e.nextSibling;
		}
		text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
	}
	var modifier = node.getAttribute("modifier");
	var c = node.getAttribute("created");
	var m = node.getAttribute("modified");
	var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
	var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
	var tags = node.getAttribute("tags");
	var fields = {};
	var attrs = node.attributes;
	for(var i = attrs.length-1; i >= 0; i--) {
		var name = attrs[i].name;
		if(attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
			fields[name] = attrs[i].value.unescapeLineBreaks();
		}
	}
	tiddler.assign(title,text,modifier,modified,tags,created,fields);
	return tiddler;
};

//--
//-- TW21Saver (inherits from SaverBase)
//--

function TW21Saver() {}

TW21Saver.prototype = new SaverBase();

TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
{
	try {
		var extendedAttributes = "";
		var usePre = config.options.chkUsePreForStorage;
		store.forEachField(tiddler,
			function(tiddler,fieldName,value) {
				// don't store stuff from the temp namespace
				if(typeof value != "string")
					value = "";
				if(!fieldName.match(/^temp\./))
					extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
			},true);
		var created = tiddler.created;
		var modified = tiddler.modified;
		var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
		attributes += (usePre && created == version.date) ? "" :' created="' + created.convertToYYYYMMDDHHMM() + '"';
		attributes += (usePre && modified == created) ? "" : ' modified="' + modified.convertToYYYYMMDDHHMM() +'"';
		var tags = tiddler.getTags();
		if(!usePre || tags)
			attributes += ' tags="' + tags.htmlEncode() + '"';
		return ('<div %0="%1"%2%3>%4</'+'div>').format([
				usePre ? "title" : "tiddler",
				tiddler.title.htmlEncode(),
				attributes,
				extendedAttributes,
				usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
			]);
	} catch (ex) {
		throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
	}
};

//]]>
</script>
<script type="text/javascript">
//<![CDATA[
if(useJavaSaver)
	document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
//]]>
</script>
<!--POST-SCRIPT-START-->

<!--POST-SCRIPT-END-->
</body>
</html>
